Firefox 3.6 での XPath による要素取得2010年01月24日 22時44分

Firefox 3.6 にしたら、動的に生成した文書からの XPath による要素取得ができなくなったという報告が挙がっています。

この原因は、Firefox 3.6 で HTML 要素の名前空間の扱いが変わったことにあります。Firefox 3.6 (Gecko 1.9.2) では、HTML5 に従い、HTML 要素が (XML 文書中でなくても) XHTML の名前空間 (HTML5 でいうところの「HTML の名前空間」) http://www.w3.org/1999/xhtml に属するようになりました。

これに伴って、これまでは XML 文書中でも有効だった、XPath 式の評価における HTML 要素の特別扱い (要素名の大文字小文字を区別しない、非修飾名を HTML の名前空間に属するものとみなす) が、HTML 文書中でのみ有効となったようです。なお、この特別扱いは、XPath 1.0 に違反しますが、HTML5 で認められています

document.implementation.createDocument(null, 'html', null) で作成されるのは XML 文書なので、接頭辞なしの XPath 式では HTML 要素を取得できなくなってしまいました。このことは、「HTMLDocument の動的な生成」でも、(こちらは Firefox 3.7a ですが) createDocument メソッドでの名前空間なし XPath の結果が×になっていることで確認可能です。

解決策としては、HTML 文書を作成するようにすればいい話です。これには XSLT の HTML 出力を使ってもいいのですが、なぜか今現在の Firefox nightly (3.7a1pre 20100123) ではこの方法がうまくいかないので、createDocument メソッドに文書型宣言と名前空間を指定するほうをおすすめします。

ちなみに AutoPagerize では document.cloneNode メソッドを使っていますが、これは読み込む文書が基本的に同じ Web サイト内のものであり、現在の文書が HTML 文書なら次も HTML 文書、XML 文書なら次も XML 文書と推定できるからです。XML 文書から HTML 文書を読み込む場合など、汎用性を考えるならやはり createDocument メソッドを使ったほうがいいでしょう。

第 2 回 WCAG 勉強会 @ 関西2009年12月30日 16時23分

先日開かれた WCAG 勉強会 @ 関西 (Twitter) の第 2 回勉強会に参加しました。

WCAG 2.0 とは

Web アクセシビリティに関する指針です。W3C が発効しており、本文といくつかの関連文書からなります。これを基に JIS X 8341-3 (Web コンテンツ JIS) が改定される予定です。

Web Content Accessibility Guidelines (WCAG) 2.0 (日本語訳)
ここでは「本文」と呼びます。4 つの原則と 12 の指針からなりますが、要点だけ書かれているのでやや読みづらいです。
Understanding WCAG 2.0 (日本語訳)
本文の各指針に対して、そのような指針を設けた意図や、補足の説明、個々の事例などが書かれています。「解説書」と呼ばれますが、こっちこそ本文という感じです。
Techniques for WCAG 2.0
HTML や JavaScript といった既存の技術を、どう使えば各指針に対応できるのか、また逆にどう使うと指針に対応していないとみなされてしまうのか、が記述されています。「技術書」と呼ばれます。
How to Meet WCAG 2.0
「クイックリファレンス」とも呼ばれます。使用する技術や達成水準を指定することで、各指針に対応する本文、解説書、技術書の項目を探し出せます。

本文は時代の変化にも対応できるように書かれていますが、解説書や技術書は一般的な環境の移り変わりにつれて随時更新されるかもしれないそうです。

勉強会の流れ

今回の範囲は「原則 1: 知覚可能」の「ガイドライン 1.1: 代替テキスト」までで、事前に本文と解説書の該当箇所を読んでおくこととなっていました。当日は、各自が範囲内の事項に関する疑問点を、付箋紙に書き出してホワイトボードに貼り付け、それらをひとつずつ、あるいはいくつかまとめて全員で議論するという形でした。

Kanasan.JS でもそうですが、全員でひとつのことについて議論するというのは、一度にさまざまな視点が得られて面白いです。

メディアと代替テキスト

ガイドライン 1.1 代替テキスト:すべての非テキストコンテンツには代替テキストを提供して、拡大印刷、点字、音声、シンボル、平易な言葉などのような、ユーザが必要とする形式に変換できるようにする。

ここでいう「代替テキスト」とは、あるメディア (媒体) から別のメディアに変換するための、中間メディアのことです。画像から点字へ、音声から字幕へ、直に変換できれば苦労はないのですが、現状ではそれが困難なため、テキストというメディアを用意することで変換にかかる負担を軽減しようというのです。

この変換は、必ずしも自動で行われるわけではありません。一定の精度を確保するためには人力での変換が必要、ということもまだまだ多いようです。

シンボルメディア

メディアの例として「シンボル」とありますが、これはアイコンやピクトグラムの組み合わせなどで文意を表すことを指すようです。実際にアイコンを並べてニュースを伝えるニュースサイト (サンプルページ、PDF) もあるほか、漫画の新聞も一種のシンボル化ではないかと思います。

感覚的なコンテンツ

WCAG 2.0 では、特定の場合には中間メディアたる代替テキストを提供しなくてもいいとされています。そのひとつが感覚的なコンテンツで、たとえばコンサートの動画があったとき、最低限「そこに動画がある」「それはコンサートの動画である」といったことが識別できる代替テキストがあればよく、「そのコンサートを聞いてどう感じたか」などの主観的な情報は必ずしも含む必要はありません。

類似文字の使用に関する注意

技術書で失敗例として挙げられているのが、代替テキスト無しでの類似文字の使用です。これは、ある文字の代わりに見た目のよく似た文字を使うことで、ひらがなの「し」の代わりにギリシャ文字の小文字イオタ (ι) を使う、漢字「神」の代わりにカタカナと漢字を並べて「ネ申」と書く、といった例があります。このことは、文字を直接ソーステキストに埋め込んだ場合だけでなく、HTML の文字参照や JavaScript のエスケープシーケンスなどで間接的に埋め込んだ場合にも当てはまります。

ただし、何が何でもダメというわけではなく、適切な代替テキストが指定されていればいいようです。技術書には、代替テキストの指定方法として、abbr 要素とその title 属性を使った例が載っています。

<abbr title="化物語">イヒ牛勿言吾</abbr>

アクセシビリティ・サポーテッド

WCAG 2.0 での重要な概念が「アクセシビリティ・サポーテッド」(本文解説書) です。これは、コンテンツが実在の支援技術 (拡大鏡、スクリーンリーダ、代替ポインティングデバイスなど) で利用可能でなければいけないということで、技術書の中にも特定のブラウザや支援技術に関する注意書きが見られます。

点字ディスプレイ

持ち運びできる円盤式点字ディスプレイがヒットしているそうです。あらかじめテキストを記憶しておいて電車の中で読むといったことが可能で、音声よりも速く読めるとか。

音訳マニュアル

代替テキストを点字や音声に変換する際の指針として、音訳マニュアルが役に立つそうです。点字では、単に読みをそのまま抜き出せばいいというわけではなく、「橋」「箸」「端」はそれぞれ区別がつくよう説明を加えるなど、さまざまなノウハウが必要とされるようでした。

ナビゲーションスキップ

サイトのヘッダやサイト全体にまたがるナビゲーションなど、ページの本文とは関係ない部分を読み飛ばせるような仕組みをブロックスキップといいます。ブロックスキップの実現手法の一つとして、ページの先頭に本文へのリンク (視覚メディアではしばしば非表示にされる) を付けるナビゲーションスキップ (スキップリンク) があります。

しかし、少なくとも勉強会参加者の周辺では、ナビゲーションスキップを使っている人はいないようでした。ナビゲーションスキップはサイトごとに異なるので使いづらい、Yahoo! や Google などよく見るページでは本文より前のリンクを飛ばす操作に慣れている、適切な見出しと支援技術の見出しジャンプ機構があれば十分、といった背景があるそうです。

雑感

実際に業務として Web アクセシビリティに携わっている方々の話を聞け、大変参考になりました。知らないこと、わかっていなかったことがほとんどで、まさしく聞くと見るとは大違いです。Web 製作に関わる方々はこの勉強会に参加して損はないと思います。

Web アクセシビリティと聞くと、ぽっと出の新しくてよくわからないものと思う方もいるかもしれませんが、障害者が情報に触れる手段に関しては古くからさまざまな試みがなされてきたこと、そしてそれらの積み重ねが Web にも応用できるという点は非常に重要なことだと思います。アクセシビリティを何か特別なものとみなすのではなく、ノウハウの蓄積された、すぐそこにある日常として受け入れることが大切です。

関連リンク

HTMLDocument の動的な作成2009年10月29日 06時11分

ブラウザ上で、JavaScript を使って HTML のソースから HTML 文書を生成するのに、どんな方法があるのか調べました。なお、以下のスクリプトは HTML 文書上で実行することが前提です。

  1. 表の見方
  2. XSLT の HTML 出力
  3. createHTMLDocument メソッド
  4. createDocument メソッド
  5. createDocument メソッドと名前空間の指定
  6. createDocument メソッドと文書型宣言の指定
  7. createDocument メソッドと文書型宣言及び名前空間の指定
  8. cloneNode メソッド
  9. iframe 要素
  10. ActiveXObject
  11. CID からの作成
  12. まとめ

表の見方

以下の表において、各項目の意味は次の通りです。

doc
HTML 文書を作成できれば○、XML 文書を作成できれば△、それ以外なら×です。HTML 文書かどうかは、createElement メソッドが HTML 要素を作成するかどうかで判断しています。
doc.title
文書ノードの title プロパティから title 要素の内容を取得できれば○、できなければ×です。
doc.body
文書ノードの body プロパティから body 要素を取得できれば○、できなければ×です。
名前空間なし XPath
//p のような名前空間接頭辞の付かない XPath 式で HTML 要素を取得できれば○、できなければ×です。
名前空間付き XPath
//h:p のような名前空間接頭辞の付いた XPath 式で HTML 要素を取得できれば○、できなければ×です。ここで、接頭辞 h は名前空間 URI http://www.w3.org/1999/xhtml に関連付けられているものとします。
innerHTML
innerHTML プロパティに <p>hello&nbsp;world のような、整形式 XML ではなく、かつ HTML の実体参照を含んだ文字列を設定して、エラーが出なければ○、エラーになれば×です。
スクリプト
script 要素で指定されたスクリプトが実行されれば○、されなければ×です。

また、使用したブラウザは次の通りです。いずれも Windows 版です。

  • Firefox 3.0.11
  • Firefox 3.5.3
  • Minefield (Firefox の開発版) 3.7a1pre、HTML5 パーサ無効
  • Minefield 3.7a1pre、HTML5 パーサ有効 (html5.enabletrue に設定)
  • Opera 9.64
  • Opera 10.00
  • Safari 3.2.3
  • Safari 4.0.3
  • Internet Explorer 6 SP3
  • Internet Explorer 7
  • Internet Explorer 8

調査には次のファイルを使いました。

XSLT の HTML 出力

function createHTMLDocument_XSLT(source) {
  var processor = new XSLTProcessor();
  var sheet = new DOMParser().parseFromString(
    '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">' +
      '<xsl:output method="html"/>' +
      '<xsl:template match="/">' +
        '<html><head><title></title></head><body></body></html>' +
      '</xsl:template>' +
    '</xsl:stylesheet>',
    'application/xml'
  );
  processor.importStylesheet(sheet);
  var doc = processor.transformToDocument(sheet);
  var range = doc.createRange();
  range.selectNodeContents(doc.documentElement);
  range.deleteContents();
  doc.documentElement.appendChild(range.createContextualFragment(source));
  return doc;
}
Firefox Safari Opera
3 3.5 3.7a 3.7a HTML5 3.2 4 9.6 10
doc
doc.title × × × ×
doc.body × × × × ×
名前空間なし XPath
名前空間付き XPath × × × ×
innerHTML
スクリプト × × × × × × × ×

比較的以前から使われている方法です。XSLT を使ってある文書を HTML 文書に変換し、後は innerHTML なり Range#createContextualFragment なりでソースを流し込みます。Firefox 2 (Gecko 1.8.1) 以前では、フラグメント識別子付きの URL 上でエラーが出るそうです。

XSLTProcessorDOMParserRange#createContextualFragment は Mozilla による独自拡張ですが、Safari (WebKit) と Opera もサポートしています。ブラウザによっては createContextualFragment を使ったパースで、head 要素や body 要素が消えてしまうことがあります。

createHTMLDocument メソッド

function createHTMLDocument_createHTMLDocument(source) {
  var doc = document.implementation.createHTMLDocument('');
  var range = doc.createRange();
  range.selectNodeContents(doc.documentElement);
  range.deleteContents();
  doc.documentElement.appendChild(range.createContextualFragment(source));
  return doc;
}
Firefox Safari Opera
3 3.5 3.7a 3.7a HTML5 3.2 4 9.6 10
doc × × × ×
doc.title × × ×
doc.body × ×
名前空間なし XPath
名前空間付き XPath × ×
innerHTML
スクリプト × × × ×

Safari と Opera は、HTML 文書を直接生成するその名も createHTMLDocument メソッドを実装しています。このメソッドは、DOM 2 HTML 2002 年 6 月 5 日付勧告候補までは存在したのですが、以降は削除され勧告には含まれていません。しかし、HTML5 草案で復活したので、将来的には他のブラウザでも実装されるかもしれません。

createDocument メソッド

function createHTMLDocument_createDocument(source) {
  var doc = document.implementation.createDocument(null, 'html', null);
  var range = document.createRange();
  range.selectNodeContents(document.documentElement);
  var content = doc.adoptNode(range.createContextualFragment(source));
  doc.documentElement.appendChild(content);
  return doc;
}
Firefox Safari Opera
3 3.5 3.7a 3.7a HTML5 3.2 4 9.6 10
doc
doc.title × × × × ×
doc.body × × × × × × × ×
名前空間なし XPath × × × ×
名前空間付き XPath × × × ×
innerHTML × × × × × ×
スクリプト × × × × × × × ×

XML 文書を生成するための createDocument メソッドを使います。これにより生成される文書はあくまでも XML 文書なので、HTML ソースのパースには呼び出し元の HTML 文書を使っています。異なる文書で生成されたノードを文書ツリーに追加するため adoptNode を使っていますが、Firefox 2 以下では対応していないので代わりに importNode を使う必要があります。

createDocument メソッドと名前空間の指定

function createHTMLDocument_createDocument_NS(source) {
  var XHTML_NS = 'http://www.w3.org/1999/xhtml';
  var doc = document.implementation.createDocument(XHTML_NS, 'html', null);
  var range = document.createRange();
  range.selectNodeContents(document.documentElement);
  var content = doc.adoptNode(range.createContextualFragment(source));
  doc.documentElement.appendChild(content);
  return doc;
}
Firefox Safari Opera
3 3.5 3.7a 3.7a HTML5 3.2 4 9.6 10
doc
doc.title × × × ×
doc.body × × × × × ×
名前空間なし XPath × × × ×
名前空間付き XPath × × × ×
innerHTML × × × × × ×
スクリプト × × × × × × × ×

Safari と Opera では、createDocument で文書要素 (ルート要素) に XHTML の名前空間を指定すると、XHTML 文書が生成されます。このとき、Safari では innerHTML に整形式 XML の断片しか設定できません。

createDocument メソッドと文書型宣言の指定

function createHTMLDocument_createDocument_DTD(source) {
  var doctype = document.implementation.createDocumentType('html',
    '-//W3C//DTD HTML 4.01//EN', 'http://www.w3.org/TR/html4/strict.dtd');
  var doc = document.implementation.createDocument(null, 'html', doctype);
  var range = document.createRange();
  range.selectNodeContents(document.documentElement);
  var content = doc.adoptNode(range.createContextualFragment(source));
  doc.documentElement.appendChild(content);
  return doc;
}
Firefox Safari Opera
3 3.5 3.7a 3.7a HTML5 3.2 4 9.6 10
doc
doc.title × × × × ×
doc.body × × × × × × × ×
名前空間なし XPath × ×
名前空間付き XPath × × × ×
innerHTML × × ×
スクリプト × × × × × × × ×

DOM 3 Core では、createDocument で文書型宣言を指定すると、その文書型宣言に見合った文書を作成してもよい (may) ことになっています。Firefox 3.5 (Gecko 1.9.1) 以降ではこれに従って、HTML 4 の文書型宣言に対しては HTML 文書が、XHTML 1.0 の文書型宣言に対しては XHTML 文書が生成されます。

createDocument メソッドと文書型宣言及び名前空間の指定

function createHTMLDocument_createDocument_DTD_NS(source) {
  var XHTML_NS = 'http://www.w3.org/1999/xhtml';
  var doctype = document.implementation.createDocumentType('html',
    '-//W3C//DTD HTML 4.01//EN', 'http://www.w3.org/TR/html4/strict.dtd');
  var doc = document.implementation.createDocument(XHTML_NS, 'html', doctype);
  var range = document.createRange();
  range.selectNodeContents(document.documentElement);
  var content = doc.adoptNode(range.createContextualFragment(source));
  doc.documentElement.appendChild(content);
  return doc;
}
Firefox Safari Opera
3 3.5 3.7a 3.7a HTML5 3.2 4 9.6 10
doc
doc.title × × × ×
doc.body × × × × ×
名前空間なし XPath × ×
名前空間付き XPath × × × ×
innerHTML × × ×
スクリプト × × × × × × × ×

HTML 4 の文書型宣言と XHTML の名前空間を両方指定した場合です。

cloneNode メソッド

function createHTMLDocument_cloneNode(source) {
  var doc = document.cloneNode(false);
  doc.appendChild(doc.importNode(document.documentElement, false));
  var range = document.createRange();
  range.selectNodeContents(document.documentElement);
  var content = doc.adoptNode(range.createContextualFragment(source));
  doc.documentElement.appendChild(content);
  return doc;
}
Firefox Safari Opera
3 3.5 3.7a 3.7a HTML5 3.2 4 9.6 10
doc × × × × ×
doc.title
doc.body × ×
名前空間なし XPath
名前空間付き XPath ×
innerHTML
スクリプト × × ×

DOM 3 Core では、文書ノードに対する cloneNode の動作は実装依存となっていますが、Firefox 3.5 以降では文書ノードを複製できます。なお、cloneNode は例外を投げないことになっていますが、Opera では文書ノードを複製しようとすると例外が発生します。

iframe 要素

function createHTMLDocument_iframe(source) {
  var iframe = document.createElement('iframe');
  document.body.appendChild(iframe);
  var doc = iframe.contentWindow.document;
  doc.open();
  doc.write(source);
  doc.close();
  document.body.removeChild(iframe);
  return doc;
}
Internet Explorer Firefox Safari Opera
6 7 8 3 3.5 3.7a 3.7a HTML5 3.2 4 9.6 10
doc ×
doc.title
doc.body
名前空間なし XPath
名前空間付き XPath × × ×
innerHTML
スクリプト

iframe 要素を使って HTML 文書を生成します。IE でも動きます。iframe 要素を文書ツリーに追加しないと、docnull になってしまうことがあります。Opera 9.6 では、スクリプトは実行されるものの、特定の操作 (document.write など) を行うと HTML 文書をうまく取得できなくなってしまうようです。また、Opera 10 では、iframe 要素の load イベントなどを見て非同期にアクセスしないと、うまく HTML 文書が取れないようです。

ActiveXObject

function createHTMLDocument_ActiveXObject(source) {
  var doc = new ActiveXObject('htmlfile');
  doc.open();
  doc.write(source);
  doc.close();
  return doc;
}
Internet Explorer
6 7 8
doc
doc.title
doc.body
innerHTML
スクリプト

IE でのみ使用可能です。フラグメント識別子付きの URL を参照する iframe 要素を含むソースを指定すると、挙動がおかしくなるそうです。

CID からの作成

const NS_HTMLDOCUMENT_CID = '{5d0fcdd0-4daa-11d2-b328-00805f8a3859}';
var doc = Components.classesByID[NS_HTMLDOCUMENT_CID].createInstance();

Firefox で、さらには拡張機能や userChrome.js スクリプトといった chrome 権限を持ったスクリプトでのみ可能な方法です。xpcshell のような document オブジェクトが存在しない環境でも HTML 文書を作成できます。この CID は、少なくとも Firefox 1.0 から 3.7a1pre まで変更されていません。これにより作られた文書は空なので、自分で要素を作って挿入する必要があります。

ちなみに、この方法を知ったのは、xpcshellでHTMLDocumentって無理なのかなぁ - The Other Road Ahead をきっかけに HTML 文書の作り方を調べていたときで、次のような経緯によります。

  1. Twitter: なんかコントラクトIDから直接 Documentを生成する手段があった気が → "@mozilla.org/dom"でMXR検索 → 見つからない → @mozilla\.org/.*document でGoogleソースコード検索 →
  2. Twitter: "@mozilla.org/xul/xul-document;1"を発見 → その付近のソースコードを見る → NS_HTMLDOCUMENT_CID を発見 → MXR検索でCIDの実際の値を確認 → とりあえずcreateInstance() → なんかできた ← 今ここ。

まとめ

  • createHTMLDocument が使えるなら、それを使うのが楽です。
  • Firefox では、作った HTML 文書をどう扱いたいのかによって、作成手段が異なってきます。XPath で要素を取りたいだけなら createDocument でもいいですし、Firefox 3 以下でも HTMLDocument 固有の機能を使いたいというのなら XSLT を使ったほうがいいかもしれません。
  • head 要素や body 要素がなくなるのが気になるなら、自分で作る必要があります。

Kanasan.JS JavaScript 第 5 版読書会 #82009年09月30日 03時41分

Kanasan.JS JavaScript 第 5 版読書会 #8 に行ってきました。今回は初めての京都での開催、それも町家でという風情あふれる読書会。真夏真冬はちょっとつらいかもしれませんが、春秋はこういった場所での勉強会というのも乙なものですね。名古屋の DeLLa.JS では茶室での開催もあったそうでうらやましいことです。

ちなみに会場「お結び庵」の運営は home's vi ("h" を抜くと「おむすび」) という法人によるものだそうで。なんとなく名前に親近感を感じてしまうのは気のせいでしょうか。

読書範囲は「17.5 キーイベント」から「18章 フォームとフォーム要素」まで。ブラウザごとに変な癖があるということが少なく (完全にばらばらか結構統一されているか)、さくさくと読めました。

キーボードショートカット

サイ本では、Web アプリケーションはキーボードショートカットをサポートすべきといっており、WCAG (ウェブコンテンツ・アクセシビリティ・ガイドライン) 2.0 (日本語訳) にもすべての機能をキーボードから利用できるようにするという指針があります。

しかし、製作者側でのショートカットキーの指定は、(特にアルファベットを含む場合) ブラウザやユーザー側で設定されたショートカットキーとの衝突や、サイト間での一貫性の欠如といった問題をはらんでいます。

個人的に、製作者側は、キーボードアクセス可能にするにあたって、

Tab
ウィジェット間の移動
矢印
ウィジェット内の項目の移動
Enter
確定、実行
スペース
状態の切り替え
Delete
削除

といったある程度意味の定まったキーに対しては動作を指定すべきだが、アルファベットを含むショートカットキーは使用を避けるか、少なくとも無効にできるようにすべきだと思っています。

関連リンク

自前でキーボードショートカットを実装するウェブサイトは、キーボードでブラウジングする人のことをもっと考えてあげてください - by edvakf in hatena
Web サイトが提供するキーボードショートカットを使うのは、普段からキーボードでブラウジングしている人たちであるという指摘と、キーボードショートカットを提供するスクリプトの書き方について。
accesskeyにはアクセシビリティが無い - Archiva
HTML の accesskey 属性の問題点に関して。
accesskeyの使い方を比較(Google,Yahoo!,MySpace,mixi,モバゲー,ニコ動,etc) | 携帯サイトをつくろう。
携帯サイトではショートカットキーがある程度統一されているという話。

delete 演算子の使用

「17.6 onload イベント」のサンプルコードで、不要になったプロパティを delete 演算子で削除していますが、個人的には null を代入するほうが好みです。

最近の JavaScript エンジンでは、同じ構造を持つオブジェクト同士は同一の「クラス」に属するものとして扱います。あるオブジェクトのプロパティを delete 演算子を使って削除すると、そのオブジェクトの構造と属する「クラス」が変化することになり、速度的に不利になる場合があります。とはいってもスクリプト全体の実行時間からすれば気にするほどではなく、どちらを選ぶかは趣味の問題でしょう。

テキスト入力に対するイベント

テキスト入力欄 (input type="text"、textarea) の入力値の変化を通知するものとして change イベントがありますが、これは入力欄からフォーカスが外れるタイミングでしか発生しません。キー入力に逐次対応したいときは keydown、keypress イベントを、それ以外の手段による変更にも対応したいときはさらにタイマー (setTimeout、setInterval) を組み合わせるのが一般的なようです。

手段を問わず (少なくとも一定時間内の) 入力値の変化を監視するために、HTML5 では input イベントが定義されています。Firefox 1.0、Safari 3、Opera 9 以上が対応していますが、Opera では (10.00の時点でも) 切り取りやドラッグ & ドロップによる値の変更に反応しない (その場合フォーカスが外れた時点で input イベントが発生する)、WebKit (Safari、Chrome) では IME で未確定の入力にも反応するなど、実装にばらつきがあります。

ちなみに、切り取り、貼り付けに反応するものとして、IE の cutpaste イベントがあり、Firefox 3 以上、Safari もこれを実装しています (element.onpaste - MDC)。このイベントはバブルしますが、IE では文書要素 (html 要素) までしかバブルしないようです。

name 属性の名前空間

サイ本 18.3.1 節 (日本語版 p. 466) には

<form name="everything">

document.everything

で参照する例が出てきますが、この方法では document のプロパティ名とフォーム名が競合したときに困るので、name 属性ではなく id 属性を使い、document.getElementById などでアクセスしたほうがいいです。

勘違いされがちですが、form、img、a 要素の name 属性が要素を一意に識別するためのものなのに対し、input、textarea、select 要素の name 属性はサーバーに送信するコントロール名を指定するためのものと、同名の属性でも役割がまったく異なります。form 要素に限らず、前者に関しては id 属性を使ったほうが、他の要素との一貫性も取れて楽でしょう。

ただし、IE 7 以下では name 属性も id 属性と同じ名前空間に入ってくる (document.getElementById("foo") で name="foo" な要素が返ることがある) ので、フォームコントロールの name 属性と、ある要素の id 属性とが同じ値を取らないようにしたほうがいいかもしれません。

フォームコントロールの値中での改行

フォームコントロールの値が改行を含むとき、その改行コードはブラウザによってさばらばらです。IE の場合、value プロパティに \r\n を含む文字列を設定しても、取得時にはすべて \r\n になっています。このような状況に対応するため、私は次のように改行コードを統一することがあります。

var value = control.value.replace(/\r\n?/g, "\n");

なお、この改行コード変換用正規表現は、Perl では正しくないそうですが、JavaScript では問題ないと思います。

IE での属性セレクタ

IE 7 以上は CSS の属性セレクタに対応していますが、スクリプトから属性に変更を加えても表示が更新されないというバグがあります (IE 8 でも発生)。その場合でも class 属性などをいじると、それまでの変更が表示に反映されるようです。

<style type="text/css">
[title="foo"] { color: #c63; }
</style>
element.setAttribute('title', 'foo');
// IE では、ここで終了すると element の色が変化しない

element.className += '';
// この時点で element の色が変化する

Kanasan.JS JSDeferred コードリーディング2009年06月30日 23時55分

すっかり記事を書くのが遅れてしまいましたが、Kanasan.JS JSDeferred コードリーディングへ行ってきました (参加者ブログ記事一覧)。JSDeferredcho45 さん作の、非同期処理を簡単に記述するためのライブラリです。簡単なリファレンスもありますが、実際に動かせるサンプルのほうがどんなものか感覚をつかめると思います。

Deferred オブジェクト

JSDeferred では、ひとつの処理をひとつの Deferred オブジェクトとして表現し、処理の流れは Deferred オブジェクトを順につなげた Deferred チェーンで表します。Deferred オブジェクトは三つのプロパティを持ちます。

callback.ok プロパティ
(正常) 処理の本体を表す関数。
callback.ng プロパティ
例外処理の本体を表す関数。
_next プロパティ
Deferred チェーン上の次の処理 (継続) を表す Deferred オブジェクト。

さらに、Deferred オブジェクトの実行状態を意識すると、JSDeferred を使って書かれたコードが読みやすくなるのではないかと思います。

待機 (idle) 状態
処理が実行されない状態。call/fail メソッドが呼び出されると実行中状態へ移る。
実行待ち (waiting) 状態
処理がそのうち実行される状態。cancel メソッドが呼び出されると待機状態へ移る。
実行中 (running) 状態
正常処理または例外処理が実行されている状態。処理本体の実行が終了したら待機状態へ移る。このとき、処理本体の返り値が Deferred オブジェクトなら、その Deferred オブジェクトの継続 (_next プロパティ) を自身の継続と同じにする。そうでなければ、自身の継続たる Deferred オブジェクトを実行する (実行中状態へ移す)。

わかったこと、注意すべきこと

  • 単に new Deferred() または Deferred() とすると、待機状態の Deferred オブジェクトが作られる。
  • 処理本体で Deferred オブジェクトが返されたなら、自身の継続を (直接は) 実行しない。
  • parallel 関数は飛ばして、先に next 関数、wait 関数を読んだほうがよさそう。
  • next 関数 (Deferred.next) と next メソッド (Deferred.prototype.next) は別物。
    • next 関数は、実行待ち状態の Deferred オブジェクトを新規作成する。引数に渡された関数がその Deferred オブジェクトの処理本体となる。
    • next メソッドは、待機状態の Deferred オブジェクトを新規作成し、それを自身の継続とする。引数に渡された関数がその Deferred オブジェクトの処理本体となる。自身の実行状態は変化しない。
  • next 関数については、とりあえず next_default だけを見れば十分。
  • 他の記事では、「JSDeferredがやっとわかった - by edvakf in hatena」が詳細でわかりやすい。

Deferred チェーンの図示

next(function f() {
  ...
  return next(function g() {
    ...
  });
}).
next(function h() {
  ...
});

は、最初に次のような Deferred チェーンを作ります。

[図 1: 上記コードが最初に作る Deferred チェーン]

ここで、Deferred チェーン先頭の Deferred オブジェクトの処理が終了し、f が Deferred オブジェクトを返すと、Deferred チェーンは次のようになります。

[図 2: 上記コードが f 実行後に作る Deferred チェーン]

h (を処理本体とする Deferred オブジェクト) は f (を処理本体とする Deferred オブジェクト) の継続として実行されるわけではありませんが、g (を処理本体とする Deferred オブジェクト) の継続として実行されるので、結局 fgh の順番で処理が進むことになります。

また、f で待機状態の Deferred オブジェクトを返せば、処理の流れをいったん f で止めておき、明示的にその Deferred オブジェクトの call メソッドを呼び出したときに h から処理の流れを再開するといったことができます。