目次

オブジェクトなJavaScriptの基礎講座

スタイルシート切り替え(オブジェクト指向)

◆ はじめに

CSS(スタイルシート)を勉強していて、たまたま、Piroさんの所にSSS.jsと云うスタイルシート切り替えスクリプトを見つけ、実装しましたが、動きがおかしいので、ロジックをみてみたら、まったく理解不能でした。(以下はすでに対処済みです。)

理解不能は、Piroさんのロジックが悪いのでなく、私の頭が悪くて(勉強不足)、理解が不能でした。

そこで、このロジックをちょっと調べてみました。すると、いろいろわかってきました。 なんか、JavaScriptのオブジェクト指向に関係があるみたいなので、その辺の勉強をしてみました。 そして、SSS.jsを解析しながら、ここに、その勉強の成果を発表いたします。

◆ まずは、予備知識です。

配列の代入文と関数は、普通以下のようになります。

<script type="text/javascript">

var a = new Object();
a[0] = "xxxx";

alert(a[0]);
alert(a["0"]);           //上と同じ結果になる
//alert(a.0);            //変数の一文字目は、英字または、アンダーバーなので、これはダメ

a["chaichan"] = "yyyy";

alert(a["chaichan"]);
alert(a.chaichan);       //こちらは、一文字目が c なので大丈夫、そして、上と同じ結果になる


function test1(){
	test1.aaa="123456789";
}
new test1();
alert(test1.aaa);

test2 = function(){      //実は関数も代入文だった!
	test2.bbb="987654321";
}
new test2();             //とにかく、new演算子でオブジェクトが生成されると理解してください
alert(test2.bbb);

</script>

上記のように、配列の代入文のインデックスの数値は、実は、文字列(連想配列)だったのです。
また、文字列の一文字目が英字または、アンダーバーときは、a["chaichan"]をa.chaichanの様に記述しても同じです。

一方、関数は、普通、function test(){ と記述していますが、実は、testという変数(オブジェクト型)に関数そのものを代入していたのです。そして、new演算子でそのオブジェクト型を実行するとオブジェクトが出来上がります。

つまり、オブジェクトは、プロパティとメソッドからなり、プロパティには連想配列のインデックスを用いるコトが出来、メソッドには、関数を用いるコトができます。

あと、スタイルシート切り替えについて少し、普通のスタイルシート指定は、link要素でCSSファイルを読み込みます。 これを固定ではなく、JavaScriptでCSSファイルを選択して、document.writeでlink要素を書き込み、CSSファイルを読み込みます。と同時にクッキーへは、選択情報を書き込みます。選択以外の時は、クッキーから選択情報を読み込み該当CSSファイル指定のlink要素をdocument.writeで書き込み、CSSファイルを読み込みます。

以上を予備知識にして、SSS.jsのロジックを見ていきましょう!

◆ SSS.jsのロジックリスト

/***************************************************************
SSS ( Style Sheets Selector :略してスリーエス) Ver.3.1
	Piro / outsider reflex (http://www.cc-net.or.jp/~piro/)
	(解説: http://www.cc-net.or.jp/~piro/tips/page/p0024.html )

	発想の基本は Dicros! の切り替えスクリプト、
	Cookie はとほほの WWW 入門と検索で見つけたサンプル( URI 失念)を
	参考にしました。
	三日坊主++の部屋の情報もかなり参考にしてます。
	でもって Ver.3.x 以降は、優乃氏によって書き直されたものを
	JavaScript 素人の僕がさらに書き直す(っていうか汚しただけ?)という
	まったくもって訳の分からんことになっております。

	・オオグマ氏 / Dicros! ( http://coolo.junbi.net/dicros/ )
	・杜甫々氏 / とほほの WWW 入門 ( http://tohoho.wakusei.ne.jp/ )
	・Kan 氏 / 三日坊主++の部屋 ( http://east.portland.ne.jp/~sigekazu/ )
	・優乃氏 / Virgo ( http://www.skipup.com/~peace/ )
	以上の諸氏に激感謝!
***************************************************************/

function SSS() {
	//-----------------------------------------------------
	// 基本設定
	//-----------------------------------------------------

	// 各シートのファイルパスの規準ディレクトリ
	SSS.rootPath = 'http://www.cc-net.or.jp/~piro/common/styles/';

	SSS.sheetsInitialize = function() {
	// シートの設定

		Sheet('Sheet1', 'strict/strict.css');
		Sheet('Sheet2', 'orange/orange.css', 'screen');
		Sheet('Sheet1@Special', 'strict/strict.css,sp.css');
		Sheet('Sheet2@Special', 'orange/orange.css,sp.css', 'screen');

	};

	// NN4.x 専用のシートのパス(複数指定可)
	SSS.NN4Sheets = 'none';

	// HTML 3.2/HTML 4 →「0」
	// XHTML 1.0 →「1」
	// XHTML 1.1 →「2」
	SSS.modeXhtml = 0;


	//-----------------------------------------------------

	// 選択されなかったシートを代替シートとして出力する場合は「 true 」
	SSS.modeAlternate = false;

	// 選択シート情報のクッキーの保持期限(日)
	SSS.cookieLimit = 30;

	// シートの選択リスト中で、デフォルトのシート名の後に表示する文字列
	SSS.defaultStr = '(Default)';

	// 隠しシート機能を無効にする場合は「false」
	SSS.modeHidden = true;

	// 隠しシートを disabled 状態でリストに表示する場合は「 true 」
	SSS.viewDisabled = false;

	//-----------------------------------------------------



	//-----------------------------------------------------
	// 初期化

	SSS.xhtmlTagClose = (SSS.modeXhtml > 0) ? ' /' : '' ;
	SSS.defaultSheetIndex = 1;

	SSS.counter = 0;
	SSS.NN4Num = 0;

	SSS.param = unescape(location.search);
		SSS.param = SSS.param.substring(1, SSS.param.length);

	//-----------------------------------------------------


	//-----------------------------------------------------
	// スタイルの判別及び link 要素の出力
	SSS.writeHeader = function() {

		Sheet('No-Style');
		SSS.sheetsInitialize();
			SSS.NN4Num = SSS.counter;
		Sheet('With-Style', SSS.NN4Sheets);

		// 選択スタイルの情報を取得( with Cookie )
		SSS.selectedId = JCookie.data.Selected;
		if (!SSS.selectedId
			|| (!UA.NN4 && SSS.selectedId == sheets[SSS.NN4Num].id))
			SSS.selectedIf = sheets[SSS.defaultSheetIndex].id;
		for (var i = 0; i != SSS.NN4Num; i++) {
			if (SSS.param == sheets[i].id) SSS.selectedId = sheets[i].id;
			if (SSS.param == sheets[i].id+'-FORCE') {
				SSS.selectedId = sheets[i].id;
				JCookie.put('Selected', SSS.selectedId, 30);
			}
		}

		// スタイル判別と link 要素の出力
		if (SSS.selectedId == sheets[0].id) sheets[0].selected = true;
		if (UA.NN4) {
			if (SSS.NN4Sheets != 'none' && !sheets[0].selected) {
				sheets[SSS.NN4Num].selected = true;
				SSS.makeLink(true, sheets[SSS.NN4Num].id, SSS.NN4Sheets);
			}
		} else {
			if (!sheets[0].selected) {
				for (i = 1; SSS.selectedId != sheets[i].id; i++)
					if (i == SSS.NN4Num) {
						i = SSS.defaultSheetIndex;
						break;
					}
				sheets[i].selected = true;
			}
			for (i = 1; i != SSS.NN4Num; i++)
				SSS.makeLink(sheets[i].selected, sheets[i].id, sheets[i].path, sheets[i].media);
		}

	}

	// linking stylesheet
	SSS.makeLink = function(selected, title, paths, media) {
		if (paths == '') return;
		if (!media) media = (UA.NN4) ? 'screen' : 'all' ;
		var titleStr = (title) ? '|title|'+title : '' ,
			alternateStr = (selected) ? '' : 'alternate ' ,
			splitedPaths = new Array();
		splitedPaths = paths.split(',');
		for ( var i = 0; i != splitedPaths.length; i++) {
			if (!(!SSS.modeAlternate && !selected))
				document.write(
					makeNode('link|type|text/css|rel|'+alternateStr+'stylesheet|href|'+SSS.rootPath+splitedPaths[i]+titleStr+'|media|'+media)
				);
		}
	}
	//-----------------------------------------------------

	//-----------------------------------------------------
	// 選択フォームの出力
	SSS.writeForm = function() {
		SSS.styleForm = (UA.NN4 && SSS.NN4Sheets == 'none') ? '' :
			SSS.styleForm = makeNode('div|id|StyleSel',
				makeNode('label|accesskey|S',
					makeNode('kbd|class|key','[S]')+' StyleSelect:'+
					makeNode('select|id|Styles|name|Styles|onchange|SSS.formApply(this);', SSS.makeOptions())
					)
				);

		var nameStr = (SSS.modeXhtml > 1) ? '' : 'name|StyleSelForm|' ;

		document.write(
			makeNode('form|'+nameStr+'id|StyleSelForm|action|'+location.href,SSS.styleForm)
		);
	}

	// 送信
	SSS.formApply = function(obj) {
		JCookie.put('Selected', sheets[obj.options[obj.selectedIndex].value].id, SSS.cookieLimit);
		location.href = location.href.match(/[^#?]*/);
	}


	SSS.makeOptions = function() {
		var optgroupOpen = false,
			options = '',
			optgroup = '';

		if (UA.NN4) options = SSS.makeOption(SSS.NN4Num);
		else {
			for ( var n = 1; n != SSS.NN4Num; n++) {
				if (sheets[n].group != sheets[n-1].group) {
					options += (optgroupOpen && optgroup != '' && !UA.N6) ?
						makeNode('optgroup|label|'+sheets[n-1].group, optgroup) : optgroup ;
					optgroup = '';
					if (sheets[n].group != '') optgroupOpen = true;
				}
				if (!sheets[n].hidden) optgroup += SSS.makeOption(n);
			}
			options += (optgroupOpen && optgroup != '' && !UA.N6) ?
				makeNode('optgroup|label|'+sheets[n-1].group, optgroup) : optgroup ;
		}

		return(options+SSS.makeOption(0));
	}

	SSS.makeOption = function(num) {
		var selAtt = (sheets[num].selected) ? '|selected|selected' : '' ;
			selAtt += (sheets[num].disabled) ? '|disabled|disabled' : '' ;
		var defValue = (num == SSS.defaultSheetIndex) ? SSS.defaultStr : '' ;
		return(makeNode('option|value|'+num+'|label|'+sheets[num].label+selAtt, sheets[num].id+defValue));
	}
	//-----------------------------------------------------

	// おまけ:スタイルが選択されているか否かを判別する関数(未使用)
	SSS.selected = function(name) { return (SSS.selectedId == name); }

} new SSS();




// シート定義
var sheets = new Array();
function Sheet(label, path, media, Default, hide) {
	var n = SSS.counter;
	if (Default) SSS.defaultSheetIndex = n;

	sheets[n] = new Array();

	sheets[n].id = label;
	sheets[n].label = label;
	sheets[n].path = path;
	sheets[n].group = '';
	sheets[n].media = (media) ? media : false ;
	if (SSS.modeHidden) {
		sheets[n].disabled = (SSS.viewDisabled && hide) ? true : false ;
		sheets[n].hidden = (SSS.viewDisabled) ? false : hide ;
	} else {
		sheets[n].disabled = false;
		sheets[n].hidden = false;
	}

	sheets[n].selected = false;

	var at = label.indexOf('@');
	if (at > -1) {
		sheets[n].group = label.substring(at+1, label.length);
		sheets[n].label = label.substring(0, at);
	}

	SSS.counter++;

}


// UA 判別(汎用)
function UA() {
	UA.strings = navigator.userAgent;

	UA.IE55 = (UA.strings.indexOf('MSIE 5.5') > -1
		|| UA.strings.indexOf('MSIE 6') > -1);
	UA.NN4 = (document.layers);
	UA.N6 = (UA.strings.indexOf('Netscape6/6.0') > -1);


	UA.type = 'others';
	if (UA.strings.indexOf('MSIE 5') > -1) UA.type = 'IE5';
	else if (UA.strings.indexOf('Gecko') > -1) UA.type = 'Mozilla';
	else if (document.all) UA.type = 'IE4';
	else if (UA.NN4) UA.type = 'NN4';

} new UA();


// クッキー関係の処理(汎用)
function JCookie() {

	JCookie.data = new Array();
	JCookie.string = (document.cookie) ? document.cookie.split(';') : new Array() ;

	for (var i = 0; i != JCookie.string.length; i++) {
		JCookie.data[JCookie.string[i].split('=')[0].match(/[^ ].*/)]
			= JCookie.string[i].split('=')[1];
	}

	JCookie.put = function(name ,data ,limit) {
		var date = '';
		if (limit) {
			today = new Date();
			today.setTime(today.getTime()+1000*60*60*24*limit);
			date = ';expires='+today.toGMTString();
		}
		document.cookie = name+'='+data+';path=/'+date;
	}

} new JCookie();


// 要素を生成(汎用)
function makeNode(param, content) {
	if (param == '') return content;
	var element = param,
		attsString = '';
	if (param.indexOf('|') > -1) {
		var atts = param.split('|');
		element = atts[0];
		for (var i = 1; i < atts.length; i+=2)
			attsString += ' '+atts[i]+'="'+atts[i+1]+'"';
	}
	return (!content) ?
		'<'+element+attsString+SSS.xhtmlTagClose+'>\n' :
		'<'+element+attsString+'>'+content+'<\/'+element+'>\n';
}


SSS.writeHeader();

◆ SSS.jsのロジックの解析のポイント

先頭から順にポイントだけ説明していきます。(全ての説明は、結構大変)

function SSS() {

普通の関数宣言ですが、実は、JavaScriptには宣言はありません。すべて実行文なのです。なんたってインタプリタですから...。 そして、これは、SSS云う変数(オブジェクト型)に関数オブジェクトを代入するという意味です。 また、関数オブジェクトとは関数の中身そのものです。では、中身を見ていきましょう。

	SSS.rootPath = 'http://www.cc-net.or.jp/~piro/common/styles/';

私は、いきなり、ここで、思考がとまってしまいました。 これを理解するには、JavaScriptの連想配列を理解しないとわかりません。 普通の配列は、たとえば、 a[0] = 123; のようになります。 しかし、JavaScriptの内部では、インデックスはすべて文字列で処理されます。

つまり、a[0] = 123; は a["0"] = 123; と等価なのです。つまり、インデックスは、文字列なのです。 さらに、a["0"] = 123; は a.0 = 123;と表現していいことになっています。 ただし、文字列の一文字目は、英字または、アンダーバーでないと文法エラーになりますので、a.0 = 123;は文法エラーです。しかし、a["B0"] = 123; は a.B0 = 123;でOKです。 ですので、上記の代入文は、実は、SSS["rootPath"] = 'http://www.cc-net.or.jp/~piro/common/styles/';と等価です。

しかし、SSS.rootPathのように表現することで、関数オブジェクトのプロパティへの代入のイメージになるわけです。そして、JavaScriptの変数.インデックスに値を代入するとインデックスが関数オブジェクトのプロパティになると理解してください。

また、変数名(SSS)が関数オブジェクトを代入した関数名(SSS)と同じにするということは、実は、script要素直下に書かれた関数は、グローバル変数で、その変数に代入しているってことです。わかりますか〜。

	SSS.sheetsInitialize = function() {
	// シートの設定

		Sheet('Sheet1', 'strict/strict.css');
		Sheet('Sheet2', 'orange/orange.css', 'screen');
		Sheet('Sheet1@Special', 'strict/strict.css,sp.css');
		Sheet('Sheet2@Special', 'orange/orange.css,sp.css', 'screen');

	};

今度は、JavaScriptの変数(SSS).インデックス(sheetsInitialize)に関数オブジェクトを代入するとインデックスが関数オブジェクトのメソッドになります。

つまり、sheetsInitializeインデックスがメソッドになり、new演算子でインスタンスされた後は、他から、SSS.sheetsInitialize();でメソッド起動が出来るようになります。

これ以降は、同じように、プロパティとメソッドを作成(定義)しています。そして、つぎの文は、

} new SSS();

new演算子でSSSをオブジェクトとして生成しています。これで、上記で作成したプロパティとメソッドがインスタンスされます。

つぎに、SSSオブジェクトのメソッドのサマリーを以下に示します。
詳細は、ご自分で解析してみてください。きっと実力がつきますよ!

SSSオブジェクト以外のオブジェクトと関数のサマリーを以下に示します。

◆ 導入と設置

以下は、piroさんの所からの引用です。

シート切り替えを行いたいページ全ての 内に、このスクリプト(SSS.js)を埋め込んでください。外部ファイルで参照しても、ページの中に直接埋め込んでも OK です。

スクリプトを埋め込んだページ内の好きな位置に以下のような記述を埋め込めば、その場に切り替えのためのフォームが表示されます。

<script type="text/javascript">
<!--
    SSS.writeForm()
-->
</script>

実際に設置する場合は、こんな感じになると思います。

 <!DOCTYPE 〜 >
<html>
 <head>
<script type="text/javascript" charset="Shift_JIS" src="SSS.js"></script>
 </head>
 <body>
<script type="text/javascript" charset="Shift_JIS">
<!--
	SSS.writeForm();
-->
</script>
 </body>
</html> 

フォームは、 body 要素の中であれば好きな位置に埋め込んで構いません。ただし、 p 要素などの中に入れるのは HTML の文法的に宜しくないので、なるべく body 要素の直下に置くようにしてください。

サンプル

◆ おわりに

今まで、私はJavaScritで、C言語のようにコーディングしてきましたが、C++的な感じにも出来ることを今回実感しました。

本コンテンツを作成するにあたって以下のサイトを参考にさせて頂きました。ありがとうございました。

尚、本スクリプトの公開は、オリジナル作成者 Piro氏の許可を頂いております。ここに謹んでお礼申し上げます。

目次