JavaScriptでモーダルダイアログをシミュレートする

前回の記事jxDialogを作成しましたが、ちょっと調べてみると随所でModalBoxという同様のライブラリが紹介されています。すでにあったのですね。なんかショック。せっかくなので、jxDialogが何をしているか説明してみます。実装の話になります。

このライブラリではモーダルダイアログをJavaScriptでシミュレートしています。一般的に行われているようなレイヤーを画面全体に被せるだけの実装だと、タブキーでフォーカスを移動する事ができてしまって、完全なモーダルダイアログ(他の操作不可)にはなりません。

問題はタブキー

タブキーのイベントがWindowまで伝搬してしまうと、フォーカスが移動してしまいます。そこで、タブのイベントをWindowまでバグリングしないように設定します。下がそのコード


if (this.isWin()) this.observe(window, 'keydown', function(event) {_this.disableTab(event);});
else if (this.isMac()) this.observe(window, 'keypress', function(event) {_this.disableTab(event);});
else this.observe(window, 'keypress', function(event) {_this.disableTab(event);});

Base.prototype.disableTab = function(event) {
event = event || window.event;
if (event.keyCode == 9/* TAB */) {
// if focused element is window, focus first focusElements
if (this.focusElements && this.focusElements[0]) this.focusElements[0].focus();
console.log(this.focusElements[0]);
this.eventStop(event);
}
};
Windowsの場合にはkeydownイベントを取得してイベントをストップします。ただし、Macintoshの場合にはなぜかkeypressイベントをストップさせないといけませんでした。ソースコードまでは追ってはいないですが、OSのAPIの違いでしょうか。こうすることで、デフォルトのタブキーの挙動は抑止されるので、ダイアログを表示したときにダイアログのformエレメントにフォーカスを当てておけば、レイヤーの下にフォーカスが移ることはないので、モーダルダイアログをシミューレートする事ができます。

タブキーも使いたい

ただし、インターネットのユーザはおそらくタブでも各エレメントをタブで移動したいと思うので、ダイアログに含まれるinputタグ、selectタグaタグのタブキーイベントを拾って、次の要素にフォーカスするように設定します。下のようにフォーカス移動を各フォームエレメントのキーイベントに割り当てます


Base.prototype.focusControl = function() {
var _this = this;
this.focusElements = this.getFocusElements(this.dialog);
for (var i = 0, len = this.focusElements.length; i < len; i++) {
var currentElement = this.focusElements[i];
var nextIndex = (i == len - 1 ? 0 : i + 1);
var prevIndex = (i == 0 ? len - 1 : i - 1);
var nextElement = this.focusElements[nextIndex];
var prevElement = this.focusElements[prevIndex];
var moveFocus = (function(n, p) {
return function(event) {
event = event || window.event;
if (event.keyCode == TAB_KEY || (event.charCode == 25 && event.shiftKey)/* for safari */) {
if (event.shiftKey) p.focus();
else n.focus();
_this.eventStop(event);
}
}
})(nextElement, prevElement);

// stop keydown event in windows
if (this.isWin()) this.observe(currentElement, 'keydown', moveFocus);
// stop keypress event in macintosh
else if (this.isMac()) this.observe(currentElement, 'keypress', moveFocus);
// other os
else this.observe(currentElement, 'keypress', moveFocus);
}
};

Base.prototype.getFocusElements = function(parentNode) {
var list = [];
var children = parentNode.childNodes;
for (var i = 0, len = children.length; i < len; i++) {
var node = children[i];
if (node.nodeType != ELEMENT_NODE) continue;
var tagName = node.tagName.toLowerCase();
switch(tagName) {
case 'input':
case 'select':
case 'a':
list.push(node);
break;
default:
Array.prototype.push.apply(list, this.getFocusElements(node));
}
}
return list;
};

windowまでキーイベントがバグリングした場合には、先ほどのdisableTab関数で最初のフォーカスエレメントにフォーカスを当てています。

以上で、モーダルダイアログをシミュレートすることができます。ただし、プログラムを組む側は継続スタイルでプログラムを使わなければいけないので、ブラウザ標準のshowModalDialogのような使い勝手にはなりません。ちなみにFirefox3からshowModalDialogが使えるようになってるらしいです。