Hatena::ブログ(Diary)

latest log このページをアンテナに追加 RSSフィード

2008-06-20

XPathの"@checked"と CSS2の":checked", setAttribute()は控えめに。

CSS2の擬似クラスセレクタに、":checked" というものがあります。

<input type="radio" checked="checked" /> や <input type="checkbox" checked="checked" /> などがヒットします。

何も知らないから XPath で表現してみたかった。

uupaa.jsは、CSSセレクタ(Level1〜3 + jQuery独自のやつ)を、内部でXPathに変換しています。

最近は、XPathスキーなので、":checked" を '//input[@checked and @checked="checked"]' として実装してみました…が、なんか、ちゃんと動かないんです。

不思議なことに動くブラウザもあるし、どうやら状況依存な気配も感じます。

何で?

結論としては、XPathのchecked属性("@checked")は、getAttribute("checked") 的なもの(DOM - Attr)であり、JavaScriptで操作可能なプロパティ(input.checked)とは、住む世界が異なるため動かなくて当然。

ブラウザにとって、setAttribute()の結果を画面に反映させる義務は無い(らしい)ので、見栄えに関する属性をsetAttribute()してしまうと、描画結果は不定となる。

ほんと?

elm.checked = true;

とすると、チェックボックスがチェックされます。当然ですね。

でも…

elm.setAttribute("checked", "checked");

これで、チェックされてしまうブラウザがあり、

elm.removeAttribute("checked");

とすると、アンチェックされるブラウザと、チェックされたままになるブラウザがあります。

さらにいうと、

elm.checked = false;
elm.checked = true;
elm.checked = false;

の後で、

elm.setAttribute("checked", "checked");

しても、チェックされないブラウザがあることに気が付いたのは、丸2日を浪費した後でした。

悔しいから無理矢理まとめてみた。

状況により、elm.checked の値と、elm.getAttribute("checked")の値にずれが生じ、XPathの"@"が役立たずになるという表です。

elm.getAttribute("checked")の結果とelm.checked の値が一致していれば、整合性が"○"、不一致なら"×"になります。

ブラウザ直前の操作 アクションチェック状態 elm.checkedの値getの値整合性
Firefox2リロード [set] □→× false→true null→checked
リロード [set] + [remove] □→×→□ false→true→falsenull→checked→null
[change] x 2 [set] falseのまま null→checked×
[change] x 2 [set] + [remove] falseのまま null→checked→null×
IE6 リロード [set] □→× false→truenull→checked
リロード [set] + [remove] □→×→× false→truefalse→true
[change] x 2 [set] □→× false→truenull→checked
[change] x 2 [set] + [remove] □→×→× false→truefalse→true
Safari3.1リロード [set] □→× false→truenull→checked
リロード [set] + [remove] □→×→□ false→true→falsenull→checked→null
[change] x 2 [set] falseのままnull→checked×
[change] x 2 [set] + [remove] falseのままnull→checked→null×
Opera9.5 リロード [set] □→× false→truenull→checked
リロード [set] + [remove] □→×→□ false→true→falsenull→checked→null
[change] x 2 [set] □→× false→truenull→checked
[change] x 2 [set] + [remove] □→×→□ false→true→falsenull→checked→null

[change]ボタンは、elm.checked = true; と elm.checked = false を繰り返すボタンです。

[set]ボタンは、elm.setAttribute("checked", "checked"); を実行するボタンです。

[remove]ボタンは、elm.removeAttribute("checked"); を実行するボタンです。

getは elm.getAttribute("checked"); の実行結果です。

そろそろ総括。

IEシリーズは、getAttribute, setAttribute の実装が、そもそもシンタックスシュガーなので、整合性が取れているように見えるだけ。

FirefoxSafariは、チェックボックスにユーザが触れるか、JavaScriptでcheckedプロパティの値を変更するまでは、整合性を保障する仕組みが入っているように見えるが、JavaScriptプロパティを変更するかマウスチェックボックスをクリックした後は、(属性値を参照する順番が変わるため?)整合性が無くなり、XPathで取れる値と実際の画面の状態にズレが生じる。

Operaは整合性を維持する仕組みが実装されている(IEの動作を真似る必然性からか)。Operaは見えないところで頑張ってる。

SafariFirefoxは表で見る限り同じに見えるが、細かな挙動が異なる。

setAttribute()で見栄えに関する属性をいじくりまわされると、XPathが使えなくなるようです。

テストコード

<html><head><title>Pseudo Selector</title><style>
#frame { position: absolute; top: 100px; left: 100px;
         width: 28em; height: 8em; border: 1px solid gray; }
</style></head><body>
<div id="frame">
  <input id="checkbox1" type="checkbox" value="1" onclick="review()" />
    <label for="checkbox1">checkbox1</label>
  <p id="screen"></p>
</div>
<div>
  <input type="button" value="change" onclick="change()" />
  <input type="button" value="set" onclick="set()" />
  <input type="button" value="remove" onclick="remove()" />
</div>
<script>
window.onload = function() { review(); }
function elm() { return document.getElementById("checkbox1"); }
function change() {
  var e = elm();
  e.checked = !e.checked;
  review();
}
function set() {
  elm().setAttribute("checked", "checked");
  review();
}
function remove() {
  elm().removeAttribute("checked");
  review();
}
function review() {
  var e = elm(), rv = [];
  rv.push("elm.checked = ", (e.checked) ? "true" : false, "<br />",
          "elm.getAttribute(\"checked\") = ", e.getAttribute("checked"), "<br />");
  if (e.hasAttribute) {
    rv.push("e.hasAttribute(\"checked\") = ", e.hasAttribute("checked"));
  } else {
    rv.push("e.hasAttribute = function undefined");
  }
  document.getElementById("screen").innerHTML = rv.join("");
}
</script></body></html>

uupaauupaa 2008/09/01 02:37 uupaa.js version 0.5では、
CSSセレクタの擬似クラス(:enabled や :checked等)をXPath式に変換せず、専用のセレクタ(uu.pseudo)を用意することでこの現象に対する対策を行いました。
CSSセレクタで擬似クラスごとガーって書けなくなっちゃうので、多少しょんぼりな解決方法になりますが、現場を混乱させるよりはマシかなと。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト

コメントを書くには、なぞなぞ認証に回答する必要があります。

Connection: close