とことん標準に拘ったCSS切り替えスクリプト(その4)の続き。典拠の見直しとモデリング等々。
2004年3月28日まで - 徒書にてご指摘頂きましたが、1.4. Association between a style sheet and a document(Document Object Model Style Sheets)で、link要素の生成によってスタイルシートが追加できる旨が記されています。ではDOM Level 2 Style Sheetsに対応していない場合には反映されないのかという疑問が出てきますが、DOMによる構造の変更が直ちに文書に反映される事を考えれば、明言されていなくてもこれは当然のことなのかもしれません。
問題は、DOM Level 2 Style Sheetsでは外部スタイルシートの追加に関する操作が全く定義されておらず、「追加はDOM HTMLでね」と言っている点にあります。DOM Level 2 Style Sheetsでやれることと言えば、少なくともCSS切り替えに関しては、スタイルシートを参照してdisabledプロパティを変更することだけです。それでさえDOM Level 1 HTMLで同等の操作が可能ですから、もはやDOM Level 2 Style Sheetsを使う意味はありません。単に対象ブラウザを少なくするだけです。よって、要求するDOMの実装はDOM Level 1 HTML(Core込み)のみにしたいと考えました。GUIの構築を援助するメソッドに関してはLevel 2 Eventsも要求します。
結果として application/xhtml+xml な文書は無視することになりますが、こちらの為の切り替えスクリプトは、効率の観点から、全く別のものとして考えるべきだと思います。
実装判別については、「標準に拘る」わけですから、そんなものは行わずに運用者に任せる形にします。この場合、未実装の場合にエラーを投げてやるだけです。tryで括って、必要なら勝手にcatchして勝手に非標準なコードを書いてくれという突き放し系。
クライアントが既に使用しているスクリプト内のグローバル変数と名前がかぶっても変更の手間が少なくて済むよう、これは最小限に抑えます。考慮した結果、先に述べた「エラー」のコンストラクタ及び、「文書のCSS全体を表すオブジェクト」、この二つを提供することになりました。
function NotImplementedError(){}
function DocumentCSS(){}
また、関数オブジェクトそれぞれに、継承用のメソッドを追加します。具体的にはFunctionコンストラクタのprototypeにinheritメソッドを追加します。運用者のものとバッティングすると最悪ですが、より良いと確信できる方法が思い浮かばないので、取り敢えず先に進むことにします。
DocumentCSS
オブジェクト文書のCSS全体を表すオブジェクト、これを仮にDocumentCSS
と名づけます。
大事なことですが、CSS切り替えが不可能なブラウザ(この場合DOM Level 1 HTML未実装ブラウザ)には迷惑がかからないようにしておくことが肝要です。よって、DocumentCSS
は関数オブジェクトにし、呼び出さない限り中身が評価されないようにしておく必要があります。
DocumentCSS
が呼び出された際、まず最初にDOMの実装チェックを行って、未実装ならばエラーを投げます。実装されていた場合、内部で利用するコンストラクタとエラーを定義し、自分自身を初期化(メソッドやプロパティを定義)し、最後に便宜上自分自身を返します。
function DocumentCSS(){
if (!document.implementation ||
!document.implementation.hasFeature('HTML', '1.0'))
throw new NotImplementedError('"DOM Level 1 HTML" is not implemented.');
function SomeConstructor(){}
function SomeError(){}
var self = arguments.callee;
self.someProperty = '';
self.someMethod = function(){};
return self;
}
DocumentCSS
は文書のCSS全体を現すオブジェクトであり、呼び出すと自分自身を初期化します。関数オブジェクトというよりは、callableなオブジェクトと考えて設計しました。コンストラクタにしても良いのですが、インスタンスは必ず一つしかないので無意味です。
プロトタイプチェインを繋ぐ場合にはコンストラクタにしておいた方が便利ですが、例えばDocumentCSS.prototype
に各メソッドを追加するという作業は、DocumentCSS
を利用できないブラウザにとっては迷惑でしかありません。
DocumentCSS
で提供するもの主にCSSを切り替える為のAPIを提供します。
DocumentCSS.change(styleName)
changeメソッドはスタイル名(link要素のtitle属性で表される)でCSSを切り替えます。空文字列を与えると、全てのCSSが無効化されます。title属性が空文字列になっているlink要素でリンクされたCSSは有効にすることができない、という弊害あり。
DocumentCSS.currentStyle
プロパティは、現在適用されているCSSグループ(内部ではCSSGroup
オブジェクトとして表現)のスタイル名です。全てのCSSが無効になっている場合は空文字列です。
DocumentCSS.styleSheets
DocumentCSS.styleSheets
プロパティは、全てのCSSGroup
の辞書です。例えば、DocumentCSS.styleSheets['キャンパス']
は、「キャンパス」という名前のCSSを全て含んだCSSGroup
になります。そんなグループが無ければundefined
。
DocumentCSS.toSelectBox(DELETE_CK_FUNC)
DocumentCSS.toSelectBox
メソッドは、CSS切り替えの為のGUIを、select要素とbutton要素を含んだDocumentFragment
として提供します。引数にCookieを削除する関数を与えると、Cookieを削除する為のbutton要素も生成します。
DOM Level 2 Events未実装の場合、NotImplementedError
を投げます。使用する際にはtry
ブロックで括る必要があります。
DocumentCSS.createCSS(name, href, charset, media, isAlternate)
DocumentCSS.createCSS
メソッドは、新たなCSSを作成して返却します。CSSとは具体的にはDocumentCSS
内部で定義されたCSSStyleSheet
オブジェクトです。これをcss
とするなら、css.participant()
で自動的に適切なCSSGroup
に追加されます。固定CSSはname
引数を空文字列にして作成します。href
、charset
、media
は、HTMLのlink要素の属性に対応しています。isAlternate
はオプションで、代替CSSか否かを真偽値で与えます。
Cookieについては勝手にしろ、というところで落ち着きました。オマケのmain関数では一応用意する予定です。
Cookieの書き込みはDocumentCSS.currentStyle
の値をsetCookie(key, value)系の関数に渡せば良いだけだし、getCookie(key)系の関数でCookieをとってきて、DocumentCSS.change(value);
とすればCookieに基づいて切り替えられます。
典型的な使用例はこのような感じです。
window.onload = function() main{
try
{
DocumentCSS(); // 初期化
}
catch(e)
{
if (e instanceof NotImplementedError)
// DOM Level 1 HTMLが実装されていない。
return;
else
// 一応デバッグ用に。
throw e;
}
DocumentCSS.createCSS('弱視用', '../style/amblyopic.css', 'UTF-8').participant();
try
{
var dfSel = DocumentCSS.toSelectBox(); // DocumentFragment
document.body.appendChild(dfSel); // 適当
}
catch(e)
{
if (e instanceof NotImplementedError)
// DOM Level 2 Event(HTMLEvents)が実装されていない。
"pass"; // 代替となるGUIを提供する文書へのアンカーを作成するとか。
else
throw e;
}
}
今のところ、まだ一応動くレベルです。というか、実用的なものならCSS切替スクリプト - 徒委記がお勧め。私のは脱毛しそうなストレスと戦いながらJavaScriptで遊ぶのが目的です。