prototype.jsの$$で:containsとその拡張を使えるようにしてみる
「こんなセレクタが CSS にあれば便利なのにと思うセレクタ - lucky bag」を読んで、妄想してるだけでもなんなのでprototype.jsの$$で使えるようにして遊んでみました。ちなみに超やっつけですので絶対どっかおかしいと思います。一応IE6,Firefox2ではちょろっと確認しています。
prototype.jsを読み込んだ後に以下のソースを読み込みます。prototype.jsのバージョンは1.5.1_rc3です。1.5.0じゃ動きません。
Selector.patterns.pseudo = /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not|contains)(\((.*?)\))?(\b|$|\s)/; Selector.pseudos['contains'] = function(nodes, selector, root) { nodes = $A(nodes); var m; if (m = selector.match(/^\s*(["'])(.*)\1\s*$/)) { return nodes.select(function(node) { var text = []; (function collectText(node) { var childs = node.childNodes; for (var i = 0, il = childs.length; i < il; ++i) { if (childs[i].nodeType == 3) { text.push(childs[i].nodeValue); } else if (childs[i].nodeType == 1) { collectText(childs[i]); } } })(node); return text.join('').include(m[2]); }); } else { var s = new Selector(selector); return nodes.select(function(node) { return s.findElements(node).length > 0; }); } }; Selector.xpath.pseudos['contains'] = function(m) { var e = m[6], p = Selector.patterns, x = Selector.xpath, le, m, v; if (m = e.match(/^\s*(["'])(.*)\1\s*$/)) { return "[contains(.,'" + m[2] + "')]"; } var exclusion = []; while (e && le != e && (/\S/).test(e)) { le = e; for (var i in p) { if (m = e.match(p[i])) { v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m); exclusion.push("(" + v.substring(1, v.length - 1) + ")"); e = e.replace(m[0], ''); break; } } } return "[.//*[" + exclusion.join(" and ") + "]]"; }; Selector.findChildElements = function(element, expressions) { var exprs = expressions.join(','), expressions = []; exprs.scan(/(([\w#:.~>+\s-]+|\*|\[.*?\]|\(.*?\))+)\s*(,|$)/, function(m) { expressions.push(m[1].strip()); }); var results = [], h = Selector.handlers; for (var i = 0, l = expressions.length, selector; i < l; i++) { selector = new Selector(expressions[i].strip()); h.concat(results, selector.findElements(element)); } return (l > 1) ? h.unique(results) : results; };
ちなみに、例に出てた
li:contains(img) { list-style-type: none; }
を適用しようと思ったら、たとえば以下のように書かないといけません。
(function(css) { Event.observe(window, 'load', function() { for (selector in css) $$(selector).each(function(e) { e.setStyle(css[selector]); }); }); })({ 'li:contains(img)':{ 'list-style-type': 'none' } });
:contains
の引数には任意のセレクタが使えるようにしてあるので、もっと複雑な事も出来ると思います。もちろん:contains("foo")
という形も使えます。たぶん。
[2007-04-26 00:04追記] e-luckさんが「セレクタと引数が同じだとループしちゃうのかな」って言っていたのですが、このコードだと例えばli:contains(li)
は「li要素を子孫に持つli要素」を指します。そういう意味じゃないのかな? 私の認識がちょっと違ったみたいです。
[2007-04-26 10:05追記] 1.5.1_rc3用にしたかも。