LDR/Fastladderで各記事を表示時に動的にアレコレする方法

一応前回の続きみたいなもの。

というか掘り下げ。

おさらい

前作った表。LDR/Fastladder (と書くのが面倒なので以後は LDR とだけ書く) の各記事を弄る方法。

タイミング 特徴 柔軟性
COMPLATE_PRINTFEED 未完。早く作って。 極めて高そう
AFTER_PRINTFEED DOM を弄るのが重そうだし、実際にはこのタイミングでは全部のフィードが表示されていないため、setTimeout などで複数回実行しないといけない。 高い
entry_widget 将来の仕様変更に左右されにくい。 どちらとも言えない (違う! これが今日の本題)
BEFORE_PRINTFEED DOM ではなく文字列操作なので気分はラクだけど、同じフィードを何度も表示すると操作が何度も実行されてしまう。(ので既に書き換えたかどうか判定する必要がある) 中くらい
テンプレート直接弄る 基本的に実行は1回 & 速い。 低い
CSS ほぼ負荷なし。 極めて低い

一番一般的なのが AFTER_PRINTFEED でやる方法で、

register_hook('AFTER_PRINTFEED', function() {
  heads = $('right_container').getElementsByTagName('h2'); // 各記事の見出し取得
  for(var i=0;i<heads.length;i++){
    var item = heads[i].parentNode.parentNode.parentNode;
    /* 記事 (item) に関する処理 */
  }
});

みたいな感じになる。

問題点

200件とかの記事を一気に表示してしまうとブラウザが固まってしまうので、LDR では頭の3記事とかだけ最初に表示する。AFTER_PRINTFEED レジスターが呼ばれるのはその瞬間なので、上の例では全部の記事を処理しているつもりでも実は上の3つしか処理していないことになる。

だから setTimeout などで処理を不特定回数繰り返す必要がある。

僕のいつもお世話になっているこの↓スクリプトを例にとると、

このような感じ。

var blocker = function(){
    var titles = $x('//h2[@class="item_title"]/a');
    /* 処理 */
    isComp() || setTimeout(blocker, cfg.interval); ← 終了判定
}
setTimeout(blocker, cfg.interval);

これは面倒。

しかも、1秒ごとに実行するため、広告フィードも一度表示した後で隠しているのが見えてしまう。

これは情けない。

表示時に書き換えたい

例えば BEFORE_PRINTFEED を使うと、こういうことができる。

register_hook('BEFORE_PRINTFEED', function(feed){
    forEach.call(feed.items, function(item) {
        item.body = item.body.replace(/<br><\/br>/ig, '<br>');
    });
});

こちらを参考にした。

この方法のだめなところは、一度表示して通り過ぎたフィードをもう一度表示するときにも同じ処理が加えられてしまうこと。

上の例の場合は問題ないけど、例えば何か画像を追加するときとかは、何個も同じ画像が追加されてしまうことになる。(なので書き換え済みかどうか判断する処理が必要になる)

やっと本題

もっと動的に、柔軟に各記事を書き換えたい!!

そこで、前回のコメントで教えてもらった entry_widgets という API が使えることがわかった。

entry_widgets の使い道ってせいぜいソーシャルブックマーク件数を表示したりするぐらいじゃねーの? あんまり色んなことは出来ないよなあ。

と思っていたのだけど、実はこれが最も柔軟そうだった。

entry_widgets の普通じゃない使い方って

例えば、特定記事 (item) だけに class を追加したい!

とか。

img.onload, img.onerror を使う

// w は window か unsafeWindow
w.entry_widgets.add('hoge_hoge', function(feed, item){
  if( /* ほにゃらら */ ){
    // IE 以外専用 (だと思う)
    return '<img alt="" style="height:1px;" src="data:,hoge" onerror="myHandler(this)" />'; 
    // 下のは IE でも大丈夫のはず
    //return '<img alt="" style="height:1px;" src="/favicon.ico" onerror="myHandler(this)" onload="myHandler(this)" />'; 
    //return '<img alt="" style="height:1px;" src="http://0.0.0.0/" onerror="myHandler(this)" />'; 
  }else{
    return '';
  }
}, 'description');

w.myHandler = function(self){ // self は img 要素自身
  w.addClass(self.parentNode.parentNode.parentNode.parentNode.parentNode,'fuga'); // クラスを追加
  // self.parentNode.parentNode.parentNode.parentNode.parentNode これが、当該記事 (item) 
};

こんな感じ。script タグでもいけそうなんだけど、それだと「自分自身」を返すのは難しくなる。(しかも何故か Opera で動かない)

img.src に data: URI を入れても IE ではちゃんと動かないっぽいことが下の記事↓に書いてあったので、IE はその下のどちらかを使えばいいと思う。(どちらがいいか意見ある人はコメントください。favicon はほぼ絶対キャッシュしてると思うので無闇なリクエストはしないはずなんだけど。)


で、早速、上にも紹介した LDR Ad-Entry Blocker for Greasemonkey を entry_widgets を使って書き直してみた。(というかこの例はそれから写経した)

OperaFirefoxGreasemonkeySafari の GreaseKit で試した。Grease ほにゃらら用ダウンロードリンク。元スクリプトでは出来てなかった、複数の広告記事を飛ばしてスクロールするのにも対応。

IE でもソースにある通りに変更すれば動くような気がするのだけど、自身ないので誰か試したら教えてください。

まとめ

各記事を表示時に動的に、かつ柔軟に書き換えるときは entry_widget を使いましょう。

一つだけ難点があって、onerror に渡す関数をグローバルにしないといけないこと。LDR のソースはグローバル変数の海なので、一般的な名前は避けたほうがいい。


そして、あの表はこうなる。

タイミング 特徴 柔軟性
entry_widget 将来の仕様変更に左右されにくい。 めちゃくちゃ高い
COMPLATE_PRINTFEED 未完。早く作って。 極めて高そう
AFTER_PRINTFEED DOM を弄るのが重そうだし、実際にはこのタイミングでは全部のフィードが表示されていないため、setTimeout などで複数回実行しないといけない。 高い
BEFORE_PRINTFEED DOM ではなく文字列操作なので気分はラクだけど、同じフィードを何度も表示すると操作が何度も実行されてしまう。(ので既に書き換えたかどうか判定する必要がある) 中くらい
テンプレート直接弄る 基本的に実行は1回 & 速い。 低い
CSS ほぼ負荷なし。 極めて低い


それから、前に作ったスクリプト↓も上の Ad-entry-blocker-mod に対応するように書き直したので、よかったらどうぞ。