2009年4月 9日

JavaScript

JavaScript の不思議な面白さ - 第六回

  • このエントリーをはてなブックマークに追加

JavaScript フレームワークを作ろう

私はフレームワークを、ある特定のコーディングルールを実現する為の手法ともとらえています。
それがなんであれ、世にあるフレームワークには何らかのコーディングルールを満たした状態を容易に維持しながら効率よく開発する為の基盤を提供していると思います。

今回からはフレームワークを実際に作ってみましょう。
題材としてはもちろん、前回まで利用してきたプログラムを使います。

 *

前回のプログラムをご覧になった方で勘の良い方はここから「HTML 文字列の効率よい組み立て方」に話の主軸が移ってくることにお気づきだと思います。
まず対象のコードをじっと見つめてみましょう。

var HTMLParts = function(){}
HTMLParts.input = function(param){
	document.write("<div style=\"color:gray;position:absolute;z-index:2;font-size:9pt;padding:6px;\" id=\""+param.id+"desc\" onclick='document.getElementById(this.id.substring(0,this.id.length-4)).focus();'>"+param.description+"</div><input name=\""+param.name+"\" type='text' id=\""+param.id+"\" onkeydown='if(this.value==\"\"){ document.getElementById(this.id+\"desc\").style.display=\"none\";};' onfocus='if(this.className.indexOf(\"focused\")>=0){this.className=this.className.replace(\"focused\",\"\");return;}if(this.value==\"\"){ document.getElementById(this.id+\"desc\").style.display=\"none\";};' onblur='if(this.value==\"\" ){ document.getElementById(this.id+\"desc\").style.display=\"\";};' />");
}

ここで利用している document.write はこれから表示したい長い HTML 文字列を必要とします。
この HTML 文字列を効率よく奇麗な形で組み立てることが出来ればよいと考え上記を少しずつ分割していきます。文字列なので足し算でつなげば改行が入れられます。

var HTMLParts = function(){}
HTMLParts.input = function(param){
	document.write(
		"<div style=\"color:gray;position:absolute;z-index:2;font-size:9pt;padding:6px;\" id=\""+param.id+"desc\" "
		+"onclick='document.getElementById(this.id.substring(0,this.id.length-4)).focus();'"
		+">"
		+param.description
		+"</div><input name=\""
		+param.name+"\" type='text' id=\""
		+param.id+"\" "
		+"onkeydown='if(this.value==\"\"){ document.getElementById(this.id+\"desc\").style.display=\"none\";};' "
		+"onfocus='if(this.className.indexOf(\"focused\")>=0){this.className=this.className.replace(\"focused\",\"\");return;}if(this.value==\"\"){ document.getElementById(this.id+\"desc\").style.display=\"none\";};' "
		+"onblur='if(this.value==\"\" ){ document.getElementById(this.id+\"desc\").style.display=\"\";};' "
		+"/>");
}

とにもかくにも改行が入りました。
複数のプログラムが書かれた一行が、一つのプログラムが一行で書かれたものになったのでひとまず進歩といえるでしょう。

残された最大の問題点は、プログラムが文字列として表現されている点です。document.write を利用する以上仕方ないところなのですが、最低限文字列ではなく、JavaScript として記述できるところにロジックをおいて文字列で記述するところから呼び出すことにします。

// 
// 改造中!! まだ動作しません!!
//

var HTMLParts = function(){}
HTMLParts.input = function(param){
	document.write(
		"<div style=\"color:gray;position:absolute;z-index:2;font-size:9pt;padding:6px;\" id=\""+param.id+"desc\" "
		+"onclick=' ___ここに何かが必要___ '"
		+">"
		+param.description
		+"</div><input name=\""
		+param.name+"\" type='text' id=\""
		+param.id+"\" "
		+"onkeydown=' ___ここに何かが必要___ ' "
		+"onfocus=' ___ここに何かが必要___ ' "
		+"onblur=' ___ここに何かが必要___ ' "
		+"/>");
}


HTMLParts.input.prototype.onclick = function(e){
	this.ref().focus();
}

HTMLParts.input.prototype.onkeydown = function(e){
	if(this.ref().value == ""){
		this.ref("desc").style.display="none";
	}
}

HTMLParts.input.prototype.onfocus = function(e){
	if(this.ref().className.indexOf("focused")>=0){
		this.ref().className = this.ref().className.replace("focused","");
		return;
	}
	if(this.ref().value == ""){
		this.ref("desc").style.display="none";
	}
}

HTMLParts.input.prototype.onblur = function(e){
	if(this.value=="" ){
		this.ref("desc").style.display="";
	}
}

プログラムが外に出ました。

しかし、上記のプログラムは肝心の部分がかけています。つまり onclick の中身を HTMLParts.input.prototype.onclick に移動したのですが、onclick から HTMLParts.input.prototype.onclick を呼び出すプログラムがありません。

この呼び出すプログラムは少々ややこしいのでこれも HTML 上に記述するには抵抗があるのですが、上記のように分離されれば、プログラムの部分はどのイベントに対しても同じ記法で記述できます。同じ記法で記述できるならフレームワークとして分離することも可能です。

var HTMLParts = function(){}
HTMLParts.lastId = 0;
HTMLParts.instanceMap = new Object;
HTMLParts.input = function(param){
	if(!param.id){
		this.id = HTMLParts.lastId;
		HTMLParts.lastId++;
	}else{
		this.id = param.id
	}
	this.param = param;
	HTMLParts.instanceMap[this.id] = this;
	this.render();
}

HTMLParts.input.prototype.getHTML = function(){
	return '<div style="color:gray;position:absolute;z-index:2;font-size:9pt;padding:6px;"'
		+ this.bindId("desc")
		+ this.bindEvent("onclick")
		+ '>'
		+ this.param.description
		+ '</div><input name="'+this.param.name+'" type="text" '
		+ this.bindId()
		+ this.bindEvent("onblur")
		+ this.bindEvent("onkeydown")
		+ this.bindEvent("onfocus")
		+ this.bindEvent("onblur")
		+ '/>';
}

HTMLParts.input.prototype.onclick = function(e){
	this.ref().focus();
}

HTMLParts.input.prototype.onkeydown = function(e){
	if(this.ref().value == ""){
		this.ref("desc").style.display="none";
	}
}

HTMLParts.input.prototype.onfocus = function(e){
	if(this.ref().className.indexOf("focused")>=0){
		this.ref().className = this.ref().className.replace("focused","");
		return;
	}
	if(this.ref().value == ""){
		this.ref("desc").style.display="none";
	}
}

HTMLParts.input.prototype.onblur = function(e){
	if(this.ref().value=="" ){
		this.ref("desc").style.display="";
	}
}


HTMLParts.input.prototype.ref = function(id){
	return document.getElementById("HTMLParts."+this.id+(id ? "." + id : ""));
}

HTMLParts.input.prototype.bindId = function(id){
	return " id=\"HTMLParts."+this.id+(id ? "." + id : "")+"\" ";
}

HTMLParts.input.prototype.bindEvent = function(type, methodName){
	if(!methodName) methodName = type;
	return " "+type+"=\"HTMLParts.instanceMap['"+this.id+"']."+methodName+"(event ? event : window.event)\" ";
}

HTMLParts.input.prototype.render = function(){
	document.write(this.getHTML());
}

いかがでしょうか。
少々長くなりましたがずいぶんとプログラムは管理しやすくなったと思います。
まだ幾分 getHTML の部分に改善の余地があるかもしれません。関数を呼んで足し算となってい部分を replace を用いて実現するとか、文字列を innerHTML で HTML 上から取得するだとか、そういった方法があると思います。

残る作業は、この手法をほかの場面でも応用できなければフレームワークとはいえません。
そこで上記の内、本当に部品開発に必要な部分だけを残して残りを親クラスに移動して継承すれば、重要な関数軍を利用して JavaScript 部品開発を効率的に行えそうです。

次回、最終回では継承を利用してコンポーネント開発用フレームワークとして仕上げていきます。
灰色の文字の入った検索ボックスが完成するまであと少し、お付き合いください。

(Yahoo!メール開発チーム 田淵純一)

(2009/4/16追記)
>>最終回へ

Yahoo! JAPANでは情報技術を駆使して人々や社会の課題を一緒に解決していける方を募集しています。詳しくは採用情報をご覧ください。

  • このエントリーをはてなブックマークに追加