日向夏特殊応援部隊

俺様向けメモ

クロスブラウザな onhashchange イベント (作りかけ)

今更 IE8 の話なんて遅れてる感満載な訳ですが、IE8 からは onhashchange イベントと言う location.hash の値が書き変わったら発火するイベントが出来ました。onhashchange イベントについては下記参照。*1

で、これなんですが location.hash にアプリケーションの状態を保持したいなんて言う、"えいじゃっくす" 的なコンテンツの場合、かなり使えるじゃないすかと。*2

クロスブラウザで行けるかもなーと思って試しに書いてみました。まだ作りかけ。

ソースコードと解説

(function() {
  var createEvent = function() {
	var evt;

	if (document.createEvent) {
	  evt = document.createEvent("Event");
	  evt.initEvent("hashchange", true, true);
	}
	else {
	  // IE6, 7
	}

	return evt;
  };

  if (Object.prototype.watch && location.watch) { // for Mozilla
	location.watch("hash", function(prop, oldVal, newVal) {
	  var evt = createEvent();

	  if (document.body.dispatchEvent(evt)) {
		return newVal;
	  }
	  else {
		return oldVal;
	  }
	});
  }
  else if (location.__defineSetter__ && location.__lookupSetter__) { // for Opera
	var nativeSetter = location.__lookupSetter__("hash");
	try {
	  location.__defineSetter__("hash", function(val) {
		var evt = createEvent();

		if (document.body.dispatchEvent(evt)) {
		  nativeSetter.call(location, val);
		}
	  });
	}
	catch (e) { }
  }
  else {
	// for IE6, 7 and Safari
  }
})();

って訳で、当初は __lookupSetter__ して native code な setter を取得して、__defineSetter__ で wrap すると言う一本で考えてたんだけど、Firefox では無理でした。try-catch があるのは名残。
なので Firefox では watch() してみました。人生初の watch() です。

あと IE6,7 と Safari は setInterval しか無いのかなーと言う感じ。面倒だからそこは書かなかった。
さらに言うと Firefox でも Opera でもオレオレカスタムイベントを定義出来るし dispatchEvent も問題無く出来るんですね。これも初めてでした。

と言う訳で、DOM で構築された各オブジェクトに hook 的な何かを書く際に、こうした手法が使えるかもねって話でした。

問題点

  • Safari, IE6, 7 はどうするか
    • setInterval() くらいしか思いつかない
  • a[@href="#hoge"] では発火しない
    • document.body とかで待ち受ける?
  • IE6, 7 ってオレオレイベントって作れても発火出来ない記憶
    • 無理?

改訂版ソースコード (2008-12-05T18:43:25+09:00 ,id:amachang に色々教えて貰った)

(function() {
  var createEvent = function() {
	var evt;

	if (document.createEvent) {
	  evt = document.createEvent("Event");
	  evt.initEvent("hashchange", true, true);
	}
	else {
	  // IE6, 7
	}

	return evt;
  };

  if (Object.prototype.watch && location.watch) { // for Mozilla
	location.watch("hash", function(prop, oldVal, newVal) {
	  var evt = createEvent();

	  if (document.body.dispatchEvent(evt)) {
		return newVal;
	  }
	  else {
		return oldVal;
	  }
	});
  }
  else if (location.__defineSetter__ && location.__lookupSetter__) { // for Opera
	var nativeSetter = location.__lookupSetter__("hash");
	try {
	  location.__defineSetter__("hash", function(val) {
		var evt = createEvent();

		if (document.body.dispatchEvent(evt)) {
		  nativeSetter.call(location, val);
		}
	  });
	}
	catch (e) { }
  }
  else if (window.__defineGetter__) { // Safari 3
	var nativeLocation = location;
	window.__defineGetter__("location", function() {
	  return {
		get href() {
		  return nativeLocation.href;
		},
		assign: function(url) {
		  nativeLocation.assign(url);
		},
		replace: function(url) {
		  nativeLocation.replace(url);
		},
		reload: function() {
		  nativeLocation.reload();
		},
		set protocol(val) {
		  nativeLocation.protocol = val;
		  return val;
		},
		get protocol() {
		  return nativeLocation.protocol;
		},
		set host(val) {
		  nativeLocation.host = this.host = val;
		  return val;
		},
		get host() {
		  return nativeLocation.host;
		},
		set hostname(val) {
		  nativeLocation.hostname = val;
		  return val;
		},
		get hostname() {
		  return nativeLocation.hostname;
		},
		set port(val) {
		  nativeLocation.port = val;
		  return val;
		},
		get port() {
		  return nativeLocation.port;
		},
		set pathname(val) {
		  nativeLocation.pathname = val;
		  return val;
		},
		get pathname() {
		  return nativeLocation.pathname;
		},
		set search(val) {
		  nativeLocation.search = val;
		  return val;
		},
		get search() {
		  return nativeLocation.search;
		},
		set hash(val) {
		  var evt = createEvent();
		  if (document.body.dispatchEvent(evt)) {
			nativeLocation.hash = val;
		  }
		  return nativeLocation.hash;
		},
		get hash() {
		  return nativeLocation.hash;
		},
		toString: function() {
		  return nativeLocation.toString();
		}
	  };
	});
  }
  else { // for IE6, 7
  }

  window.addEventListener("hashchange", function() {
	alert("DEKITA");
  }, false);
})();

しかしそもそも戻るボタンのハンドリングがって話だったのにどれも満たせてない事にあとから気づいた二人 orz...

*1:そもそも hashchange イベントって HTML5 だったのかー!

*2:自分は余りそういうアプリケーションを作りたいとは思わないけど