フォーカスイベントとタブ切り替え

input や textarea にフォーカスがあったってる状態でタブを切り替えたときに focus, blur イベントがどのように起こるか各ブラウザを調べてみた。

テスト内容

テストページ


ページには input[type=text], textarea, button が並んでる。
各要素はフォーカスすると 要素名を書き出し、フォーカスが外れると 要素名: blur と書き出すようになっている。
やったことはテストページと別のページを開いておき、input or textarea or button のいづれかの要素にフォーカスした状態でもう一方のページに切り替え、テストページに戻るというテスト。ページの切り替えはタブバーを使った。


テストしたブラウザは IE 7, Firefox 3.0.6, Opera 9.64, Safari 3.2.2 の 4 つ。いづれWindows 環境。

結果

IE & Firefox

テストページから別のページを切り替えると blur イベントが発生。つまりフォーカスが外れる。
戻ってくると同じ要素にフォーカスがあたり focus イベントが発生。

Opera

テストページから別のページを切り替えてもフォーカスは外れない。
戻ってきてもフォーカスされたまま。
ページ切り替えでイベントは発生しない。

Safari

ちょっと挙動がつかみづらいのでもしかしたら違うかも。
テストページから別のページを切り替えるとフォーカスが外れる。ただし blur イベントが発生しない。
戻ってくるとフォーカスは外れたまま。
再びその要素にフォーカスすると blur イベント(さっき外れたときのイベントと思われる。) と focus イベントが発生。
blur イベントはその要素にフォーカスせずともページをクリックしただけでも起こる。


この違いがどのような不具合をもたらすのかがいまいち思いつかない。

$.each の落とし穴

jQuery の $.each は for で書くよりさくっとかけて便利だ。 *1
使い方は

$.each([0, 1, 2], function() { alert(this); })

のように、第1引数に Array (or Object)、 第2引数に各要素に対する処理 Function を書く。


this は それぞれの値(この例だと 0, 1, 2)が入ってくるんだけど、ここで注意すべき点は型が Number から Object に変換されること*2
つまり下のようなコード*3だと、'a', 'b', 'c' と順にアラートすると思ったら 'hoge' としかアラートされない。これは、switch が '===' で比較しているため。

$.each(['a', 'b', 'c'], function(){
             alert(typeof this) // object
	switch(this) {
		case 'a':
			alert('a')
			break;
		case 'b':
			alert('b')
			break;
		case 'c':
			alert('c')
			break;
		default:
			alert('hoge')
			break;
	}
})


型変換されずに元の値をそのまま使いたい場合は

$.each([0, 1, 2], function(i, v) {
    alert(typeof v) // number
    alert(v)
}

このように、$.each の 第2引数の関数の第2引数 (上の例だと v)を扱う必要がある。ちなみに 第一引数 (上の例だと i)はインデックス(key) が入る。


$.each 内で switch や === を使って this を比較するときは気をつけようね。

追記 1:10

Number or String が Object に変換されるってのはつまり

var n = 3
s.apply(n)
function s {
    alert(this) // 3
    alert(typeof this) // object
}

こういうことなんですね。

*1:Array.forEach() が全ブラウザで使えるようになればいいんだけど。

*2:String も Object に変換される

*3:うまい例が思いつかず。

jQuery でしましまを作るプラグイン

ヒント: http://h2ham.seesaa.net/article/114037411.html
each 内の function の引数にインデックスがわたされることがわかったので

(function($){
	$.fn.zebra = function(options){
		var default_options = {
			class_name: 'zebra',
			interval: 2
		};
		options = $.extend(default_options, options || {});

		return this.each(function(i){
			if((i + 1) % options.interval == 0)
				$(this).addClass(options.class_name);
		})
	}
})(jQuery);

使い方

// li 3つおきに 'zebra' というクラスをつける
$('#zebra-ul li').zebra({interval: 3});

// tr 2つおきに 'shimashima' というクラスをつける
$('#zebra-table tbody tr').zebra({class_name: 'shimashima');


デモ

jQuery プラグインを書くときのポイント

input に事前に値を入力しておく - higeorange's blog を例に jQueryプラグイン(メソッド追加)書き方のポイントを書いて見る。(あくまでも私の書き方)

メソッドの追加法

(function($) {
    $.fn.[メソッド名] = function() {}
})(jQuery)

という書き方をしているけど、

jQuery.fn.[メソッド名] = function() {}

でも

jQuery.fn.extend({
    '[メソッド名]': function() {}
})

でもいけるので好きな書き方で。

メソッドの引数

$.fn.preInput = function(txt, options) {
	var default_options = {
		class_name: 'pre-input'
	};

//	options = $.merge(default_options, options || {});
	options = $.extend(default_options, options || {});

ここで重要なのは "$.merge" "$.extend"。
重要な引数(ここでは txt)はひとつの引数として、その他はあってもなくてもいいようにオブジェクトとしている。
そのオブジェクトとして渡された引数を $.merge $.extend メソッドで デフォルトで指定した変数を上書きしている。
$.merge $.extend 便利!!

追記修正 2009/2/0 23:25

$.merge メソッドではなく $.extend メソッドでした。
ややこしい。

jQuery オブジェクトを返す

jQuery のメソッドはほぼすべてが jQuery オブジェクトを返す。*1
jQuery オブジェクトを返すと何が便利かというと、

$('p').css('color', 'red').hover(function() { alert('hover') }, function() {}) 

のようにメソッドを繋げて書けること。


jQuery オブジェクトを返すために

return this.each() // each メソッドも jQuery オブジェクトを返す。

としている。

each メソッド: 各要素に対する処理

this.each(function() {
  ...
  var elm = $(this);
  ...
});

each メソッド内で $() で指定した各要素に対する処理をかいていくのだけど、ここで注意することは each(function() {}) 内の this は jQuery オブジェクトではなく 各エレメントそのものであるということ。
つまり、その要素に対して jQuery のメソッドを使いたければ上のように $(this) とする必要がある。

まとめ

4 点ほど書いたけど、この中でもっとも jQuery らしくするのは 3 番目に書いた jQuery オブジェクトを返すということだと思う。
jQuery はそれ自体で便利なメソッドが十分用意されているので、それらを組み合わせてちょこちょこっと手を加えればそれらしいものが簡単にできる。お試しあれ。

*1:例外: get メソッド

input に事前に値を入力しておく

input に事前に入力値を入力しておいてフォーカスするとその値が消える jQuery プラグインを書いた。

(function($) {

$.fn.preInput = function(txt, options) {
	var default_options = {
		class_name: 'pre-input'
	};

	options = $.extend(default_options, options || {});
	return this.each(function(){
		if(typeof this.value == 'undefined') return;

		var elm = $(this);
		elm.val(txt)
		elm.addClass(options.class_name)
		elm.focus(function(){
			if(elm.val() == txt) {
				elm.removeClass(options.class_name)
				elm.val('');
			}
		});
		elm.blur(function(){
			if(elm.val() == '') {
				elm.addClass(options.class_name)
				elm.val(txt)
			}
		});
	});
}

})(jQuery);

使い方

<input id="hoge" type="text" value="" />
$('#hoge').preInput('ここに入力してね');

デモ

一つ前の jQuery.eventDelay とあわせてデモを作った。
http://labo.higeorange.com/jquery/#preinput

追記 修正 2009/2/10 0:10

コード 8 行目
$.merge -> $.extend

Zooomr 用 Autopagerize SITEINFO 書いた

フォトーク

  {
    "name": "Zooomr photalk",
    "data": {
        "pageElement": "//table[@id=\"timeline\"]",
        "url": "http://(jp|www).zooomr.com/*",
        "nextLink": "//table[@id=\"timeline\"]/following-sibling::h2[1]/a[last()]",
        "exampleUrl": "http://jp.zooomr.com/"
    }
  }

nextLink 修正 23:30

写真ページ

  {
    "name": "Zooomr photos",
    "data": {
        "pageElement": "//table[@id=\"SubNav\"]/following-sibling::table[1]/tbody[1]/tr[1]/td[1]/table[1]",
        "url": "http://(jp|www).zooomr.com/photos*",
        "nextLink": "//a[@class=\"Next\"]",
        "exampleUrl": "http://jp.zooomr.com/"
    }
  }

追記

wedata に登録しておいた。

Ajax でフォームポスト

(function($) {

$.fn.ajaxPost = function(callback, options) {
    var default_options = {
        data_type: 'html',
        before_send: function() {},
        error_handler: function() {}
    };

    options = $.extend(default_options, options)
    return this.each(function(){
        if(this.tagName.toLowerCase() != 'form') return;
        var f = $(this);
        var submit = f.find('button[@type="submit"], input[@type="submit"]')

        f.submit(function(e) {
            e.preventDefault();

            $.ajax({
                url: f.attr('action'),
                type: 'POST',
                data: f.serialize() + '&mode=ajax',
                dataType: options.data_type,
                beforeSend: function() {
                    submit.attr('disabled', 'disabled')
                    options.before_send();
                },
                success: callback,
                error: options.error_handler,
                complete: function(xhr, textStatus) {
                    submit.removeAttr('disabled')
                }
            });
        });
    });
}

})(jQuery)

使い方

<form action="/hoge" id="hoge">
  .....
</form>
$('#hoge').ajaxPost(function(data, textStatus) {
    alert(data);
});


jQuery の $.ajax はほんと便利。

追記 2009/2/12 22:50

修正。complete オプションがあったのね。

追記 2009/2/15 19:10

jQuery 1.3 からセレクタの指定の仕方が変わったので
submit ボタンを指定するところを

        var submit = f.find('button[type=submit], input[type=submit]')

と @ を使わないようにしないといけない模様。