2008年2月21日木曜日

addEventListener(), attachEvent() を使ってaddLoadEvent()

addLoadEvent() は少し古い技術なので、DOM操作で使われるaddEventListener()attachEvent() を利用して書き換えてみました。

function addLoadEvent(func) {
 if(typeof window.addEventListener == 'function'){ // addEventListenerが使えるなら
  window.addEventListener('load', func, false);
  return true;
 } else if(typeof window.attachEvent == 'object'){ // attachEventが使えるなら(IE用)
  window.attachEvent('onload', func);
  return true;
 }

 var oldonload = window.onload; // どちらも使えないなら
 if (typeof window.onload != 'function') {
  window.onload = func;
 } else {
  window.onload = function() {
   oldonload();
   func();
  }
 }
}

addEventListener() と attachEvent()

addEventListener() と attachEvent() は、DOMで扱われるノードに対するイベント(マウスクリックとか)をトリガーとして、メソッドを追加します。
addEventListener() はW3Cで定義された標準メソッドで、attachEvent() はIEの独自拡張です。IEは addEventListener() を利用することができません。
attachEvent() は追加されたメソッドの処理順がめちゃくちゃなんですが、window.onload よりは使い勝手がよいので採用しました。

window.onloadだけで作られたaddLoadEvent()との違い

まず、次のJavaScriptコードを実行してみてください。

<script type='text/javascript'>
function addLoadEvent(func) {
 var oldonload = window.onload;
 if (typeof window.onload != 'function') {
  window.onload = func;
 } else {
  window.onload = function() {
   oldonload();
   func();
  }
 }
}

function hello(){
 alert('Hello, World!');
}

function yes(){
 alert('Yes, my master.');
}

addLoadEvent(hello);
window.onload = yes;
</script>

上記コードを実行すると、yes() が実行され、hello()が実行されない結果に終わります。
addEventListener() で window.onload 追加したメソッドが window.onload = yes; によって上書きされてしまっているのです。

上書きされないようにするためには addLoadEvent() を addEventListener() を利用したコードに書き換えます。

<script type='text/javascript'>
function addLoadEvent(func) {
 if(typeof window.addEventListener == 'function'){
  window.addEventListener('load', func, false);
  return true;
 } else if(typeof window.attachEvent == 'object'){
  window.attachEvent('onload', func);
  return true;
 }

 var oldonload = window.onload;
 if (typeof window.onload != 'function') {
  window.onload = func;
 } else {
  window.onload = function() {
   oldonload();
   func();
  }
 }
}

function hello(){
 alert('Hello, World!');
}

function yes(){
 alert('Yes, my master.');
}

addLoadEvent(hello);
window.onload = yes;
</script>

これで hello(), yes() 両方の関数が有効になりました。
addEventListener() は window.onload に関数を格納しているわけではないので、window.onload = yes; が実行されて機能が上書きされる心配はありません。

どんな状況で addLoadEvent() が役に立つのか

  • 他人が書いたコードに機能を追加するとき
  • Proxomitron, Greasemonkey 等で既存サイトに機能を追加するとき

# 逆から見れば、一からJavaScriptコードを書くときには、addLoadEvent() を使わず addEventListener() を使った方がわかりやすいかもしれません。

ラベル: ,

2008年2月20日水曜日

JavaScriptでinclude

JavaScriptはVer.2.0から include が実装されるそうですが、それまでは長いコードを一つのJSファイルにまとめると管理が大変です。かといって、複数ファイルにコードを分割して、script要素を複数宣言するのも手間がかかります。
そこで、includeの代用となる関数を作りました。

// 外部JavaScriptをインクルード
function include_javascript(jspath){
 jspath = jspath.toString();

 var scriptElm = document.createElement('script');
 scriptElm.type = 'text/javascript';
 scriptElm.charset = 'utf-8';
 scriptElm.src = jspath;
 var head = document.getElementsByTagName('head')[0];
 head.appendChild(scriptElm);
}

// 外部JavaScript群をインクルード
function include_javascripts(jspaths){

 for(var key in jspaths){
  include_javascript(jspaths[key]);
 }
}

関数の使い方

1つの外部JavaScriptをincludeするには、以下のように書きます。

include_javascript('./js/hogehoge.js');

複数の外部JavaScriptをincludeするには、以下のように書きます。

var js_path = [
 './js/hogehoge1.js',
 './js/hogehoge2.js'
];
include_javascripts(js_path);

注意点

引数のJavaScriptパスはHTML文書からの相対パスか絶対パスを指定してください。(関数を呼び出したJavaScriptファイルからの相対パスではありません)

仕組み

include_javascript() は引数で指定された外部JavaScriptファイルを呼び出すscript要素を生成します。
include_javascripts() は引数で渡された配列の要素数だけ include_javascript() を繰り返します。

ラベル: ,

2008年2月19日火曜日

JavaScriptでExcelデータをtable要素に変換

(X)HTMLのtable要素は多量のデータを整理してくれる優れものですが、(X)HTMLソースからは直感的に全体像が見て取れず、それ故、編集が面倒、という理由から今まで敬遠していました。
この欠点を解消するため、Excelの表データをtable要素に変換するJavaScriptを書いてみました。

使い方

  1. Excelファイルを開き、table要素に変換したい部分を範囲選択してコピー (マウスでぐぐ~っと選択すればOK)
  2. 変換スクリプトのフォーム上に貼り付け
  3. [変換] ボタンをクリック!
  4. table要素に変換されたHTMLソースが出力される

内部動作 (アルゴリズム)

Excelからクリップボードにコピーされたデータは「タブ文字区切りのCSVフォーマット」となっており、これを array[行][列] で構成される多次元配列に変換します。
(各セルにあるデータはタブ文字で区切られているので split("\t") で良さそうに思えますが、実際にはセルデータに改行が入っているデータをコピーした時にセルデータ全体をダブルクォートで括られるケースがあり、これに対応するために少し複雑な処理をしています。)
多次元配列に変換したら、配列データを元にtable要素に変換して終わりです。

# 特殊なことはしていないつもりですが、平均よりちょっとユーザー定義関数が多めかもしれません。
# preg_replace() とか hreg_replace() は個人的趣向がかなり入っているので、人によっては読みづらいかもしれません……。

更新履歴

2008/02/19 2:00
 - 初版
2008/02/20 1:23
 - ダブルクォートで括られたセルデータが改行区切りで指定されていたとき、一つのセルデータとして扱う場合があった不具合を修正 ("hogehoge"\nhoge2")

既知の不具合/仕様

  • 結合されたセルがtable要素に反映されない。(クリップボードに格納されたデータにセルの結合を示す情報がないので、対応できません。)

ToDo

  • 1列目を項目名として扱い、[1列目のデータをth要素に変換する] オプションを追加
  • table要素からExcelデータに戻せるように変換

ラベル:

2008年2月11日月曜日

右クリック禁止を解除するProxomitronフィルタ

右クリックメニューの表示を禁止しているサイトで右クリックメニューを表示させます。
以下の3つのフィルタで実現している機能全てを含んでおり、それ以上の対策も施してあります。

  • Allow right mouse click
  • Allow right mouse click 2
  • Allow right mouse click plus
[Patterns]
Name = "Allow contextmenu event [js] [2008/02/11] test2"
Active = TRUE
URL = "$TYPE(htm)"
Limit = 8
Match = "(^(^</head>))$STOP()"
Replace = "\r\n<script type='text/javascript'>\r\n"
          "function addLoadEvent(func) {"
          " if(typeof window.addEventListener == 'function'){"
          "  window.addEventListener('load', func, false);"
          "  return true;"
          " } else if(typeof window.attachEvent == 'object'){"
          "  window.attachEvent('onload', func);"
          "  return true;"
          " }"
          ""
          " var oldonload = window.onload;"
          " if (typeof window.onload != 'function') {"
          "  window.onload = func;"
          " } else {"
          "  window.onload = function() {"
          "   oldonload();"
          "   func();"
          "  }"
          " }"
          "}"
          "function killContextmenuEvent(){"
          " if(typeof document.oncontextmenu == 'function'){"
          "  document.oncontextmenu = null;"
          " }"
          ""
          " if(typeof document.onmousedown == 'function'){"
          "  document.onmousedown = null;"
          " }"
          ""
          " var tag = document.getElementsByTagName('*');"
          ""
          " for(i = 0, L = tag.length; i < L ; i++){"
          "  tag[i].oncontextmenu = null;"
          "  if(navigator.appName == 'Microsoft Internet Explorer'){"
          "   tag[i].onmousedown = null;"
          "  }"
          " }"
          "}"
          "addLoadEvent(killContextmenuEvent);"
          "\r\n</script>\r\n"

動作検証

実際に動作しているか確認するためのテストページも作ってみました。

onmousedown について

onmousedown はマウスボタンが押されたときに発動します。
このことから、onmousedown は右クリック禁止以外の用途で使われる事があると想像でき、onmousedownに関する処理を無効化すると、右クリック禁止以外の場面で使用している重要な機能を失ってしまう可能性があります。
私の検証した範囲ではonmousedown が右クリックに反応するのはIEのみという結果だったので、このフィルタではonmousedown の無効化処理をIE限定にしています。
(マウスイベントが右クリックによるものかの判定できなかったので、やむなくブラウザの名前で判定しました)

ただし、通常の使い方で表示領域全体を監視する document.onmousedown を使用する必要性はまずないと思いますので、こちらは機能が使えれる環境であれば有効になる処理にしてあります。
もっとも、documentオブジェクトからonmousedownへのアクセスはIEの独自拡張っぽいです。

addEventListenerへの対策

addEventListenerにどうやって対策したものか、考えあぐねています。
addEventListener で追加したイベントハンドラはremoveEventListenerで解除できるようですが、メソッド名がわからなければ手が出せません。
悩んだ末に考えついた対策は次の2通り。どちらも形になるところまで進んでいません。

  • addEventListener でデフォルトアクションを発生させる処理を追加する
  • removeEventListener ですべてのイベントハンドラを削除する

attachEvent対応

IE対応を謳うならattachEventにも対応しなくてはなりませんが、力尽きたのでいずれ。
希望があれば、声をかけてください。

ラベル: ,

2008年2月10日日曜日

Google、Yahoo特殊検索のリンク先に転送するProxomitronフィルタ

「Google: Jump special search」を更新および、YahooでWeb検索したときにYahoo ダイレクト検索のリンク先に転送するフィルタを作成しました。

更新点
  • Googleの仕様変更 (<p class=e> → <div class=e>) に対応
[Patterns]
Name = "Yahoo: Redirect direct search [2008/02/08] test2"
Active = TRUE
URL = "$TYPE(htm)search.yahoo.co.jp/search(\?|/dir\?)"
Limit = 4096
Match = "(^(^$NEST(<div\s,"
        "[^>]++id=$AV(yschDD)*"
        "<a\s[^>]++href=$AV((http://wrs.search.yahoo.co.jp/*/\*-http(%3A|:)//rd.yahoo.co.jp/search/direct/*)\0)"
        "*,</div>)))$STOP()"
Replace = "\k<meta http-equiv="refresh" content="0; url=\0" />"

Name = "Google: Jump special search [2008/02/07] test1"
Active = TRUE
URL = "$TYPE(htm) www.google.co(m|.jp)/search\?"
Limit = 1024
Match = "(^(^"
        "<div\sclass=$AV(e|g(^?)$STOP()(^(^?)))>[^<]+"
        "(<(^table[ >])[^>]+>[^<]+)+"
        "<table\s[^>]+><tr><td\s[^>]+><img\s[^>]+></td><td\s[^>]+>[^<]+"
        "(<(^a\s|/td>)[^>]+>[^<]+)+<a\s[^>]++href=$AV(((/url\?url\=|)http://*)\0)[ >]"
        "))$STOP()"
Replace = "\k<meta http-equiv="refresh" content="0; url=\0" />"

ラベル: ,

2ちゃんねるスレッドで長文コメントを折りたたむProxomitronフィルタ

2ちゃんねるスレッドに投稿されている長文コメントの4行目以降を隠します。
4行目以降は「-- 続きを読む --」をクリックすることで、展開されます。

[Patterns]
Name = "2ch: Collapse long comment (1/2) [2008/02/09] test3"
Active = TRUE
URL = "$TYPE(htm)[^.]+.(2ch.net/|bbspink.com/)"
Limit = 8
Match = "(^(^</head>))$STOP()"
Replace = "\r\n<script type='text/javascript'>"
          "function addLoadEvent(func) {"
          " var oldonload = window.onload;"
          " if (typeof window.onload != 'function') {"
          "  window.onload = func;"
          " } else {"
          "  window.onload = function() {"
          "   oldonload();"
          "   func();"
          "  }"
          " }"
          "}"
          "function readAfterMsg(){"
          " var div = document.getElementsByTagName('div');"
          " for(var i=0, L=div.length; i < L; i++){"
          "  if(div[i].className == 'read_after'){"
          "   div[i].onclick = function(){"
          "    if(this.nextSibling.style.display == 'none'){"
          "     this.style.display = 'none';"
          "     this.nextSibling.style.display = 'block';"
          "    }"
          "   }"
          "  }"
          " }"
          "}"
          "addLoadEvent(readAfterMsg);"
          "\r\n</script>\r\n"

Name = "2ch: Collapse long comment (2/2) [2008/02/09] test2"
Active = TRUE
URL = "$TYPE(htm)[^.]+.(2ch.net/|bbspink.com/)"
Limit = 4096
Match = "(<dd>"
        "(((^<(br /+>|dt>|/dd>))?)+<br /+>)+{3})\0"
        "(((^<(dt>|/dd>))?)+)\1"
Replace = "\0\r\n"
          "<div class='read_after'>-- &#x7d9a;&#x304d;&#x3092;&#x8aad;&#x3080; --</div><div style='display: none;'>\1</div>\r\n"

Name = "2ch: Collapse long comment (nyo) (2/2) [2008/02/09] test2"
Active = TRUE
URL = "$TYPE(htm)[^.]+.(2ch.net/|bbspink.com/)"
Limit = 4096
Match = "(<dd>"
        "(((^<(br /+>|dt>|/dd>))?)+<br /+>)+{3})\0"
        "(((^<(dt>|/dd>))?)+)\1"
Replace = "\0\r\n"
          "<div class='read_after'>-- &#x7d9a;&#x304d;&#x3092;&#x8aad;&#x3080;&#x306b;&#x3087; --</div><div style='display: none;'>\1</div>\r\n"

2つある (2/2) は「-- 続きを読む --」の表現が違います。
機能的には変わりませんので、お好みに合わせてお使いください。

ラベル: ,

2008年2月3日日曜日

京ぽんで使える携帯サイト 覚え書き

京ぽん(WX320K)のOperaで [表示モード] を [ケータイモード] にした環境で、正常に使える携帯サイト一覧です。


■Google

Google Mobile
http://www.google.co.jp/m
Gmail Mobile
http://www.google.co.jp/m?action=addmodredirect&igtyp=20
Google トランジット モバイル
http://www.google.co.jp/transit?output=mobile&source=m
Google カレンダー モバイル
http://www.google.com/calendar/m?hl=ja
Google マップ
http://www.google.co.jp/m/lcb?mp=1&source=m
Google Mobile Proxy
http://www.google.co.jp/gwt/n


■交通情報

goo路線
http://transit.goo.ne.jp/mobile/
東急バス
http://www.tokyubus.co.jp/


■辞書

三省堂モバイル辞書
http://mobile.sanseido.net/dictionary/
Wapedia
http://wapedia.mobi/ja/?setfavlang=ja


■価格比較

価格.comモバイル
http://kakaku.com/mobile/
価格比較コネコ モバイル
http://m.coneco.net/
Bookget Mobile
http://m.bookget.net/


■通販サイト

Amazon Mobile
http://amazon.jp/
NTT-X Store モバイル
http://nttxstore.jp/i/


■特価情報

特価ネット通販情報 mobile ver.
http://www.toknet.info/i/
やすウマ [RSS]
http://6726.teacup.com/firehawk13/bbs/rss15.xml


■その他

ケータイlivedoor
http://m.livedoor.com/
教えてgooモバイル
http://m.oshiete1.goo.ne.jp/
ポケットはてな
http://mobile.hatena.ne.jp/
携帯版4Gamer
http://www.4gamer.net/ktai.html
ヘッドホンレビューズ.NET
http://headphonereviews.net/pages/user/m/


■PCでアカウント取得後、携帯専用URLを発行するWebサービス

check*pad
http://www.checkpad.jp/


## 備考
・教えて!gooモバイルはQ&A検索すると、PC版のサイトにリダイレクトされる問題があります。カテゴリから辿る分には問題ありません。
・livedoorは画像を適度に使って、色合いも優しく使いやすいです。
・ポケットはてなはあまり使ってません。

ラベル: