2010年10月29日金曜日

applyメソッドの素朴な実用例

2010年現在、JavaScript関連の仕事ではほとんどの場合においてjQueryライブラリを使うわけだが、いわゆるレガシー案件というのだろうか、環境がそれを許さない場合もある。

そういった場合、DOMの書き換え処理を素朴な方法、つまりgetElementByIDなどDOMの組み込みメソッドを使った方法で実現することになる。本当に小さいプログラムなら気にならないかもしれないが、似たようなことを何度も書くことになるので自分でちょっとしたユーティリティ関数を書いておき、いろんな仕事で使い回すことになる。

そんな中にapplyメソッドを使った実用的なものがいくつかあったのでここに記録しておく。一昔前まではapplyの意味を理解できなかったけれどもう大丈夫ですよ、という記念をかねて。あとクロージャも。

DOMユーティリティ関数

// 指定されたidをもつ要素に関数fを適用
function getByIdAndApply(id, f){
  var elm = document.getElementById(id);
  if(elm) f.apply(elm);
}
// 指定されたタグをもつ要素群に関数fを適用
function getByTagNameAndApply(tag, f){
  var elms = document.getElementsByTagName(tag);
  for(var i=0, n=elms.length; i<n; i++) f.apply(elms[i]);
}

利用例

htmlを書きかえる。スタイルも変更(無駄にタイマーを使う)。

<html>
<head>
  <script type="text/javascript" charset="utf-8" src="./js/myutil.js"></script>
  <script type="text/javascript">
  window.onload = function(){

    // id=p1 なる要素のテキストを変更
    getByIdAndApply('p1', function(){
      this.innerHTML = "foo";
    });

    // 全p要素のスタイルを変更
    getByTagNameAndApply('p', function(){
      // クロージャを使う例(setTimeout内でthisを参照するため仮引数elmとして保持)
      (function(elm){ setTimeout(function(){elm.style.color='#f00';}, 2000) })(this);
    });
  }

  </script>
</head>
<body>
  <p id="p1">text</p>
  <p id="p2">text</p>
  <p id="p3">text</p>
</body>
</html>

2010年10月27日水曜日

IETesterのTips:コマンドライン起動

Webサイトを仕事で作る場合、たいていはIEの異なるバージョンで動作をテストする工程がある。その工程で役に立つのがIETesterというソフト。このソフト一つをインストールすればIE5.5以上の各バージョンでのレンダリング結果およびJavaScriptの挙動が確認できるので、色々なバージョンのIEをインストールして共存させる必要がない、開発用PCを仮想化する必要もない。

このIETesterを起動する際、GUIでボタンをクリックしてもよいのだが、何回もボタンをクリックして、同じURLを何回も入力 or コピペ する必要があるので面倒。そこで一度URLを入力したら全バージョンのIEを起動してくれるようなインターフェースが欲しくなる。以下、CUI(コマンドライン、コマンドプロンプト、DOSプロンプト)でそれを実現する方法。

1. 起動用のバッチファイルを作る

@echo on
@start "" /b "C:\Program Files (x86)\Core Services\IETester\IETester.exe" -all %1

ここで、startコマンドの第二引数"/b"は新しいウィンドウを開かないことを表す。よって第一引数(ウィンドウタイトル)も空文字列でOK。

これをファイル名 "ietest.bat" として、デフォルトのフォルダ(c:\Users\foo など)に保存しておく。

※OSは Windows 7 Home Premium。

2. コマンドプロンプトからバッチファイルを実行(引数は表示したいURL)

C:\Users\foo> ietest.bat http://host.domain/

(補足)BASIC認証をかけてるサイトなら id, password を引数に含めればOK

C:\Users\foo> ietest.bat http://id:password@host.domain/

2010年10月26日火曜日

mousemoveイベントのブラウザごとの違い(IE&ChromeとFirefox)

マウスポインタをちょっと愛らしくしたい、なんて仕事がたまにあると思う。画像を変えて、マウスに少し遅れて追従するような動きをさせる、とか。

例えば以下のサイトのように。

これらのサイトをいろいろなブラウザで閲覧して気づいたのだが、Firefoxはほかと挙動が違う。具体的には、ページを読み込んでからマウスを動かすまで、マウスポインタに画像が追従しない。試しにFirefoxでリロードしてからマウスを動かさずにただ眺めてみよう。

これがなぜなのか少し試行錯誤してみたところ、Firefoxだけは人が実際にマウスを動かすまでは有効なmousemoveイベントオブジェクト(= clientXなどの座標値が正しくセットされたmousemoveイベントオブジェクト)が発生しないからのようだ。反対に、Firefox以外の場合は何もしなくても mousemoveイベントが定期的に発生するため、適当なタイミングでその座標値を取得し、画像を設定した要素のスタイル(position:top, position:left)を書き換えることで画像をマウスのところまで移動させることができる。

実験として次のようなhtml+スクリプトを読み込んでみる(jQuery利用)。マウスを動かさずにじっとしていれば、ブラウザの違いが確認できる。

<html>
<head>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
$(function(){
  var cnt = 0;
  function f(e){
    $('#info').text(cnt + ' : ' + e.clientY);
    cnt++;
  };
  // mousemoveイベントに上の関数をバインド
  $('body').mousemove(f);
});
</script>
</head>
<body>
<p id="info"></p>
</body>
</html>
IE, Chrome の場合は、
ロードしただけでmousemoveが定期的に発生(カウンタ表示が1秒ごとくらいに増えていく。マウスを動かしていないのに…)
したがってマウス座標(mousemoveイベントオブジェクトのclientYプロパティ)が取得できる
Firefox(3.6)の場合は、
待っていても mousemove が発生しない。座標が取れない。

待ってもイベントが発生しないのであれば、強制的に発生させてみたらどうか? そこで、上のスクリプトを1行だけ変えてtrigger()を追加してみる。

(略)
  // mousemoveイベントに上の関数をバインド。さらにtrigger()で強制的にイベントを発生させる
  $('body').mousemove(f).trigger("mousemove");
});
</script>
(略)
この結果は、
0 : undefined と表示されて終了。triggerで発生させたイベントオブジェクトには座標値がセットされないらしい。

というわけでFirefoxの場合だけ、マウスが動くまでマウス用画像は座標(0, 0)の位置で待機するという仕様になりました…

2010年10月25日月曜日

無名関数(匿名関数)を再帰的に呼び出す

JavaScriptでは、無名関数の中で "arguments.callee()"を実行することにより、無名関数を再帰的に呼び出すことができる。

※Javaをやる人の間では匿名関数と呼ぶのが主流だが、JavaScriptをやる人は無名関数と呼ぶ場合が多い?

例:ID "foo" をもつDOM要素の、5階層以上の親に当たるTABLE要素を探し出してstyle属性をクリアする。

<script type="text/javascript">
(function(id){
  var elm = document.getElementById(id); // 指定されたidをもつ要素

  if(elm){
    var cnt=0;
    var target = (function(o){ // 再帰的な無名関数
      if(cnt >= 5 && o.tagName == 'TABLE') {
        return o; // 条件を満たす要素
      } else {
        cnt++;
        return arguments.callee(o.parentNode); // 親要素に対し無名関数を適用
      }
    })(elm);
    target.style.cssText = ''; // 目的の要素のスタイルを変更
  }

})('foo');
</script>

2010年10月21日木曜日

再帰によるべき乗(累乗)の計算をいくつか

再帰によりxのn乗を求める。すごく初歩的な問題だと思うが、あらためて。

n回再帰
(defun power (x n) "X to the Nth power"
  (if (<= n 0)
      1
    (* x (power x (- n 1)))))
power
(power 2 8)
=> 256
別解その1: 2乗を利用して再帰呼び出し回数を減らす
(defun power (x n)
  (cond
   ((<= n 1) x)
   ; nが偶数の場合にnを半減させる
   ((evenp n) (funcall (lambda (x) (* x x)) (power x (/ n 2))))
   (t (* x (power x (- n 1))) )))
別解その2: さらに再帰呼び出しを減らす(Perlで書いてるけど)
sub pow3{
    my ($x, $n) = @_;
    if($n < 1){
        1;
    }elsif($n % 2 == 0){
        (sub { my $x=shift; return $x*$x; })->( pow3($x, $n / 2) );2
    }else{
        # nが奇数の場合
        $x * (sub { my $x=shift; return $x*$x; })->( pow3($x, ($n - 1) / 2) );
    }
}

たいていの人は最初の解法で満足するだろう。が、"Paradigms of Artificial Intelligence Programming" という本には2番目の解法が載っていた。すごい人は些細な問題に対してもこだわりが違うのだな、と。

2010年10月18日月曜日

lftp コマンド省略機能

FTPクライアントの"lftp"では、いくつかのコマンドを省略することができる。

  • chmod => ch
  • lcd => lc
  • lpwd => lp
  • pwd => pw

たった2、3文字だけれど、、、キータイプの量が減る。

また、省略しすぎてコマンドを特定できない場合は、以下のようなエラー(ambiguous command)となる。

lftp foo@bar.baz.org:/html> p
Ambiguous command `p'.

2010年10月14日木曜日

sed ファイル置換+バックアップ

簡単な文字列置換には手軽さゆえ sed を使ってしまう。

ファイルの中身を置換する場合、リダイレクトでテンポラリファイルを作って mv するという手順を踏んでいたが、これは無駄。オプション -i を使うことによりファイルをsedだけで(in placeに)置換できるのだった。

$ sed --help
...
  -i[SUFFIX], --in-place[=SUFFIX]
                 edit files in place (makes backup if extension supplied)

ヘルプに記述されているように、"-i"の直後にextension(拡張子)を指定すればバックアップファイルも自動生成される。

バックアップ不要のとき
sed -i 's/foo/bar/g' /tmp/baz.txt
拡張子".orig"でバックアップをとる
sed -i.orig 's/foo/bar/g' /tmp/baz.txt
応用:findと組み合わせる
find . -type f -name '*.txt' -exec sed -i.orig 's/foo/bar/g' {} \;

2010年10月7日木曜日

Mercurial(hg)でどのファイルを変更したか調べる

hg diffコマンドに -c と --stat を指定すればOK。

$ hg diff -c 26 --stat
 foo/bar.pdf                     |    0 
 foo/index.php                   |    8 +++++++-
 foo/bar/baz/search.php          |    2 +-
 foo/bar/baz/user.php            |    2 +-
 4 files changed, 9 insertions(+), 3 deletions(-)
  • -c でリビジョン番号を指定。上の例では26。
  • --stat で"diffstat"形式のサマリー表示。ファイル名のほか、追加行数(insertions)、削除行数(deletions)もわかる。

2010年10月5日火曜日

モンテカルロ法による円周率の近似(Emacs Lisp)

こちらの記事「モンテカルロ法による円周率の近似」を Elisp で真似てみただけ(Haskellは今のところさっぱりわからない)。

;; 平面内の点の座標(1未満)
(defun x ()
  (mapcar #'(lambda (x) (/ x 10000.0))
          (list (random 10000) (random 10000)) ))
x
;; 点と座標中心との距離
(defun d (x)
  (sqrt (+ (* (elt x 0) (elt x 0)) (* (elt x 1) (elt x 1)))) )
d

;; n個の数列を生成して円内にある割合を計算(数列を全部リストで持つのでだいぶ富豪的)。
;; 整数での除算が丸められるので (* n 1.0) の様にしないといけない。
(defun my-calc-pi (n)
  (* 4 (/ (apply #'+
                 (mapcar #'(lambda (x) (if (< (d x) 1) 1 0))
                         (let ((seq) (i 0))
                           (while (< (length seq) n)
                             (setq seq (cons (x) seq))
                             (incf i) )
                           seq )))
          (* n 1.0) )))
my-calc-pi
;; 10,000個の点を生成した場合
(my-calc-pi 10000)
3.1524

「モンテカルロ法」という言葉を久しぶりに目にして何だか高揚した。学生気分を取り戻した感じ。