テキスト読込型のデータベース製作工程

JavaScriptでデータベースを作ってみるサンプルです。製作過程を段階的に紹介しています。

JavaScriptのデータベースの利点と弱点

★データを読み込んでしまうので動作が機敏。(データが大きくなると読み込みに時間がかかる)
★1つのHTML内で検索処理が完結するので技術面の安全性が高い。(CSVファイルは公開されてしまうので、個人情報は扱えない)
★CGIを設置する必要がなく、データだけを更新することもできる。(ブラウザからデータを更新できない)

製作の目的

★JavaScriptでファイルの読込を行い、CGIを使わずにデータベースを運用できることを実証する
★プログラムとデータを分轄し、データはテキストファイル単独でメンテナンスを可能にする
★複数の検索機能や並び替えの機能を持ったデータベースの基本構造を製作する
★製作の過程を表示することにより、シンプルなデータベースや他の目的に転用できる素材とする

運用面での注意点

JavaScriptはソースの中身が丸見えで、使用するデータも簡単に外部から見ることができます。
そのため、顧客情報、決済情報、原価管理のデータを扱うとセキュリティー上の問題が生じてしまいますので、一般公開に適さないデータは取り扱わないようにしてください。
そのような場合はCGIを使ったり、SSL対応のサーバを使って、安全性の高い環境を利用してください。

学習用に処理速度よりも処理内容が判るように記述していますので、実際の運用時には短縮や置き換えをして処理速度が向上するコーディングをしましょう。

製作工程

Ajaxを利用した古旗氏のテキスト読込のサンプルコードをベースにして、これを改良して行きます。
下記ではソースの変更個所を赤で示しています。各HTMLファイル内では変更個所に「★」を付けておきます。
追加したスクリプト部分は変更点として明記していますので、HTML内のソースを参照してください。

オフラインではAjaxの通信機能が使えないため、必ずサーバ上にファイルを転送してテストしてください。

①テキスト受信テスト

HTML表示Firefox版

関連ファイル:xmlhttp.js / data.txt

変更点:古旗氏のサンプルコードをUTF-8で保存。

読み込むテキストファイルがUFT-8形式でないと、日本語を正しく処理できません。それに合わせて使用する全てのファイルのテキストエンコードをUTF-8にしています。

★Firefox版

Internet Explorer と Safari ではテキストを表示できますが、Firefox では表示されません。次のように修正が必要です。

function displayData()
	{
	if ((httpObj.readyState == 4) && (httpObj.status == 200))
 		{
		document.getElementById("resultData").innerHTML = httpObj.responseText;
	}else{
		document.getElementById("resultData").innerHTML = "Loading...";
	}
}

②Excelで作ったCSVファイルを読み込む

HTML表示Firefox版 / iPad版

関連ファイル:xmlhttp.js / sample.xls / sample.csv

<form name="ajaxForm">	
<input type="button" value="sample.csvファイル読み込み" onClick="loadTextFile('sample.csv')">
</form>

変更点:<input>内のonClickで呼び出すファイル名をsample.csvに変更。

sample.xlsをCSV形式で保存した後、sample.csvをテキストエディタでUTF-8に変換して保存し直しています。

★iPad版

データファイルの拡張子が「.csv」ではテキストファイルとして扱わないのか読込できないようです。拡張子を変更すると問題なく動作します。
iOS4.3では問題が解消されているようです。

<form name="ajaxForm">	
<input type="button" value="sample.txtファイル読み込み" onClick="loadTextFile('sample.txt')">
</form>

③CSVファイルを配列変数へ読み込む

HTML表示(これ以後はIEとFirefoxとも共通です)

関連ファイル:xmlhttp.js / sample2.xls / sample2.csv

function displayData()
	{
	if ((httpObj.readyState == 4) && (httpObj.status == 200))
		{
		//★CSVから配列変数へ読込
		CSVtoAD(httpObj.responseText);
//★テーブル出力 output_table(); }else{ document.getElementById("resultData").innerText = "Loading..."; } }
<form name="ajaxForm">	
<input type="button" value="sample2.csvファイル読み込み" onClick="loadTextFile('sample2.csv')">
</form>

変更点(1):ボタンの表示と、ファイル読み込み指示の流れを後のスクリプトで処理するように変更。
変更点(2):<script>を追加し、CSVtoAD()output_table()を追加。

CSVファイルの特徴。1レコードは改行(\n)まで、1フィールドはカンマ区切り(,)。フィールド内にカンマや改行がある場合、データを「"」で囲います。
フィールド内で使われた「"」はCSV出力時に「""」に変換されるため、データ読込後、「&quote;」に変換しています。
改行コードも処理前に「\n」に統一している。フィールド内の改行はテーブル表示の時点で<br>に変更します。
今回のCSVファイルでは1レコード目を項目名としています。これをそのまま項目名として再利用しています。

④簡易検索機能の組み込み

HTML表示

関連ファイル:xmlhttp.js / sample3.csv

function displayData()
	{
	if ((httpObj.readyState == 4) && (httpObj.status == 200))
		{
		//CSVから配列変数へ読込
		CSVtoAD(httpObj.responseText);
		//★セレクトメニューの初期設定
		set_select_menu();
	}
}
<body onLoad="loadTextFile('sample3.csv')">
<h1>簡易検索機能の組み込み</h1>
<form name="ajaxForm">	
<select id="selMenu" onChange="output_detail(this.selectedIndex)"></select>
</form>

変更点(1):sample3.csvには、画像のフィールドを追加しファイル名を入力。HTML読み込み完了後、ファイルを読み込でいる。
変更点(2):フォームのボタンをなくし、選択メニューに変更。読み込んだデータを元に選択メニューを表示。
変更点(3):CSVtoAD()はそのまま利用し、<script>にset_select_menu()output_detail()を追加。

データを先に読み込み、選択メニューを生成しています。選択した商品のデータはテーブルに配置されて表示します。

⑤テキスト検索機能の組み込み

HTML表示

関連ファイル:xmlhttp.js / sample3.csv

<form name="ajaxForm" onSubmit="output_result(); return false">	
<input type="text" name="searchtext"> 
<input type="submit" value="検索">
</form>

変更点(1):<form>にonSubmitを追加。<select>を<input type="text">と<input type="submit">に変更。
変更点(2):<script>にoutput_result()output_text()を追加。set_select_menu()は削除。

検索文字列を入力すると、部分検索で各フィールドを検索し、検索結果の一覧を表示します。そこから各リンクを選ぶとレコードデータを表示します。
検索時の操作として、エンターキーまたは検索ボタンで検索開始ができるようにonSubmitを利用し、ページの更新が行われないようにしています。

⑥数値の範囲検索の組み込み

HTML表示

関連ファイル:xmlhttp.js / sample4.csv

<body onLoad="loadTextFile('sample4.csv')">
//検索一覧の表示
function output_result() {
	var n,m,r;
	//検索文字列を取得
	var s = document.ajaxForm.searchtext.value;
	//検索結果
	t = "";
 
	//★検索分岐
	//検索対象フィールド
	var ffs = 3;
	//先頭の文字に「=<>」がある場合
	var k = s.substr(0,1);
	if ((k == "=") || (k == "<") || (k == ">")) {
		//2番目の文字に「=<>」がある場合
		var j = s.substr(1,1);
		if ((j == "=") || (j == "<") || (j == ">")) {
			kakaku = s.substring(2,s.length);
		} else {
			kakaku = s.substring(1,s.length);
		}
		//文字列を数値化
		if (isNaN(kakaku)) {
			alert("数値を正しく入力してください。");
			return false;
		}
		kakaku = eval(kakaku);
		//数値検索
		for (n=1;n<list.length;n++) {
			//検索ヒット
			var bool = false;
			if ((k == "<") || (j == "<")) bool = (list[n][ffs] < kakaku);
			if ((k == ">") || (j == ">")) bool = (list[n][ffs] > kakaku);
			if (! bool) {
				if ((k == "=") || (j == "=")) bool = (list[n][ffs] == kakaku);
			}
			//ヒットしたデータを取得
			if (bool) t += output_text(n);
		}
	} else {
		//テキスト検索
		for (n=1;n<list.length;n++) {
			//検索ヒット
			var bool = false;
			//[1]商品名~[6]説明文を検索
			for (m=1;m<7;m++) {
				//正規表現(大文字小文字無視)
				reg = new RegExp(s,"i")
				//部分検索
				r = list[n][m].match(reg);
				if ((r != "") && (r != null)) {
					//ヒットしたデータを取得
					t += output_text(n);
					//以後の項目は検索しない
					break;
				}
			}
		}
	}
	//結果出力
	document.getElementById("resultData").innerHTML = t;
}
//データ取得
function output_text(num) {
(省略)
	var t = "<p><a href='javascript:output_detail(" + num + ")'>" + list[num][1] + " 【" + list[num][2] + "】</a> 価格:" + format_comma(list[num][3]) + " 在庫:" + list[num][4] + "<br>" + list[num][6] + "</p>";
(省略)
}
//詳細データの表示
function output_detail(idx) {
	(省略)
	t += "<tr><td bgcolor='#ffff99'>" + list[0][3] + "</td><td align='right'>" + format_comma(list[idx][3]) + "</td></tr>";
	(省略)
}

変更点(1):数値の検索をするために価格の「,」抜き、文字列から数値に変更したsample4.csvを使用。
変更点(2):output_text()とoutput_detail()内の価格表示にカンマを追加するformat_comma()を追加。
変更点(3):output_result()内に価格検索の処理を追加。

先頭に「=<>」の記号がある場合、価格に対して数値検索をし、それ以外ではテキスト検索をするように分岐しています。以上(>=)、以下(<=)にも対応しています。
変数kakakuには正しく数字のみが入っていることを確認し、その上で文字列から数値化して比較に利用しています。
この段階では数値検索を価格に限定した固定値になっています。

⑦検索結果の並び替え

HTML表示

関連ファイル:xmlhttp.js / sample4.csv

//検索結果の取得
functionoutput_result(){
	varn,m,r;
	//検索文字列を取得
	vars=document.ajaxForm.searchtext.value;
	//検索結果
	t="";

	//★検索結果用の配列変数
	result=newArray();
	//検索分岐
	//検索対象フィールド
	varffs=3;
	//先頭の文字に「=<>」がある場合
	vark=s.substr(0,1);
	if((k=="=")||(k=="<")||(k==">")){
		//2番目の文字に「=<>」がある場合
		varj=s.substr(1,1);
		if((j=="=")||(j=="<")||(j==">")){
			kakaku=s.substring(2,s.length);
		}else{
			kakaku=s.substring(1,s.length);
		}

		//文字列を数値化
		if(isNaN(kakaku)){
			alert("数値を正しく入力してください。");
			returnfalse;
		}
		kakaku=eval(kakaku);

		//数値検索
		for(n=1;n<list.length;n++){
			//検索ヒット
			varbool=false;
			if((k=="<")||(j=="<"))bool=(list[n][ffs]<kakaku);
			if((k==">")||(j==">"))bool=(list[n][ffs]>kakaku);
			if(!bool){
				if((k=="=")||(j=="="))bool=(list[n][ffs]==kakaku);
			}
			//★ヒットしたデータを取得
			if(bool)result.push(list[n][0]);
		}

	}else{
		//テキスト検索
		for(n=1;n<list.length;n++){
			//検索ヒット
			varbool=false;
			//[1]商品名~[6]説明文を検索
			for(m=1;m<7;m++){
				//正規表現(大文字小文字無視)
				reg=newRegExp(s,"i")
				//部分検索
				r=list[n][m].match(reg);
				if((r!="")&&(r!=null)){
					//★ヒットしたデータを取得
					result.push(list[n][0]);
					//以後の項目は検索しない
					break;
				}
			}
		}
	}

	//★並び替え
	//対象フィールド
	varfbs=ffs;
	if((fbs>0)&&(result.length>0)){
		//事前処理
		varlst=newArray();
		for(n=0;n<result.length;n++){
			if(isNaN(list[result[n]][fbs])){
				//テキスト比較
				lst[n]=list[result[n]][fbs].toLowerCase();
			}else{
				//数値比較
				lst[n]=eval(list[result[n]][fbs]);
			}
		}
		//並び替え
		for(n=0;n<result.length-1;n++){
			for(m=n+1;m<result.length;m++){
				if(lst[n]>lst[m]){
					//データ番号の入れ替え
					varr=result[n];
					result[n]=result[m];
					result[m]=r;
				}
			}
		}
	}
	//★検索一覧の表示
	output_result2();
}
//詳細データの表示
functionoutput_detail(idx){
	(省略)
	vart="<p><ahref='javascript:'output_result2()'>&lt;&lt;検索一覧に戻る</a></p>";
	(省略)
}

変更点(1):output_result()内で、検索にヒットしたデータは配列変数resultに入れた後、並べ替えを行う。
変更点(2):output_result()を検索結果取得までの処理とし、結果の表示を使い回すためにoutput_result2()として分割した。
変更点(3):output_detail()内に検索結果に戻るリンクを追加。output_result2()を利用。
変更点(4):検索結果を配列変数resultに保持しているため、詳細画面から検索結果に戻るリンクを追加。

並び替えの処理において、事前処理としてデータの型に応じたデータの変換を行っています。その後の並び替えの比較には、その変換後の値を使用します。
文字の場合、アルファベットの大文字と小文字を小文字に変換してから比較しています。変換なしでは「M」と「i」では「M」が小さくなります。
数値の場合でも、その時点では文字列型の数値(数字)になっているので、数値型に変換して比較しています。変換なしでは「9000」と「10000」では「10000」が小さくなります。
検索結果を一旦別の配列変数に入れて、並び替えをしてから出力します。検索件数も表示しました。

⑧検索対象の選択

HTML表示

関連ファイル:xmlhttp.js / sample4.csv

<form name="ajaxForm" onSubmit="output_result(); return false">
<input type="text" name="searchtext"> 
<input type="submit" value="検索">
<input type="radio" name="r_ffs" value="0" checked>テキスト検索 
<input type="radio" name="r_ffs" value="3">価格検索 
<input type="radio" name="r_ffs" value="4">在庫検索 
</form>
//検索結果の取得
functionoutput_result(){
	varn,m,r;
	//検索文字列を取得
	vars=document.ajaxForm.searchtext.value;
	//検索結果
	t="";

	//検索結果用の配列変数
	result=newArray();

	//★検索対象フィールド
	varffs=document.ajaxForm.r_ffs.length;
	for(n=0;n<ffs;n++){
		if(document.ajaxForm.r_ffs[n].checked){
			ffs=document.ajaxForm.r_ffs[n].value;
			break;
		}
	}
	//検索分岐
	if(ffs>0){//★
		//先頭の文字に「=<>」がある場合
		vark=s.substr(0,1);
		if((k=="=")||(k=="<")||(k==">")){
			//2番目の文字に「=<>」がある場合
			varj=s.substr(1,1);
			if((j=="=")||(j=="<")||(j==">")){
				kakaku=s.substring(2,s.length);
			}else{
				kakaku=s.substring(1,s.length);
			}
		}else{
			//★記号未入力の場合
			k="=";
			j=k;
			kakaku=s;
		}
		//数値検索
		(以下省略)

変更点(1):価格と在庫数のどちらも検索可能なように、検索の目的を選択できるようにフォームを追加。
変更点(2):検索方法の分岐を変更し、ラジオボタンの状態によってテキスト検索と数値検索(価格検索または在庫検索)に分岐する。
変更点(3): 数値検索で、記号が未入力の場合は「=」があるとみなす。

テキスト検索を選択して検索すると、各フィールド([1]~[6])から一致する文字列を検索します。
価格検索または在庫検索を選択すると、それぞれの数値に対して、同じか大きいか小さいかの条件を付けて検索し、並び替えを行ってから表示します。
並び替えについては、検索対象の選択と連動していますが、個別に指定ができるようになっています。

⑨並び替えの順序を切替

HTML表示

//検索結果の取得
function output_result() {
 (省略)
	//★検索一覧の表示
	output_result2(0);
}
//検索結果の表示
function output_result2(num) {
	var n;

	//★検索件数
	var t = "<p>検索結果:" + result.length + "件 並び替え:<a href='javascript:output_result2(0)'>昇順</a> <a href='javascript:output_result2(1)'>降順</a></p>";

	//検索結果
	if (result.length > 0) {
		//テーブル
		for (n=0;n<result.length;n++) {
			//★取り出し順の選択
			if (num > 0) t += output_text(result[result.length-1-n]);
			else t += output_text(result[n]);
		}
	}

	//結果出力
	document.getElementById("resultData").innerHTML = t;
}

変更点:output_result2()で、検索結果の並び順を選択できるように変更。

検索後に、並び順を降順か昇順に切り替えるようにリンクを追加しました。2つのリンクからは並び順を示す値が渡されます。
受け取ったoutput_result2()内では、降順の場合に配列変数result内の値を逆順に取り出しています。

⑩並び替え項目を選択可能にする

HTML表示

//検索結果の取得
function output_result() {
	(省略)
	//★並び替え
	sort_result(ffs);
	//検索一覧の表示
	output_result2(0);
}
//★並び替え
function sort_result(ffs) {
	var n,m;
	//★対象フィールド
	var fbs = 0;
	if ((num > 0) && (num < 5)) fbs = ffs; 
	if ((fbs > 0) && (result.length > 0)) {
		(省略)
	}
}
//検索結果の表示
function output_result2(num) {
	var n;
 
	//★検索件数
	var t = "<p>検索結果:" + result.length + "件 並び替え:";
	for (n=1;n<5;n++) {
		if (n > 1) t += " / ";
			t += list[0][n] + " <a href='javascript:sort_result(" + n + "); output_result2(0)'>昇順</a> <a href='javascript:sort_result(" + n + "); output_result2(1)'>降順</a>";
		}
		t += "</p>";
		//検索結果
		(省略) 

変更点(1):並び替えの処理をsort_result()としてoutput_result()より分割。
変更点(2):並び替えの対象フィールドを商品番号[1]、商品名[2]、価格[3]、在庫[4]の4つに設定し、それ以外(標準値)ではデータ番号[0]を使用する。
変更点(3):並び替えのメニューを対象フィールドと同じ数に増やしている。

検索結果の画面に8つの並び替えのメニューを置いて、リンクをクリックすると並び替えと表示の処理を指示通りに行います。

デバッグ時の注意点

output_result()の処理中にエラーが発生した場合、その後のreturn falseが処理されず、フォームの送信が発生してしまいます。

<form name="ajaxForm" onSubmit="output_result(); return false">
<input type="text" name="searchtext"> 
<input type="submit" value="検索">
</form>

上記のフォームを次のように修正するとエラーの時点で止まります。上記の場合でもURLに変化があるので気が付くと思いますが、エラーの表示は消えてしまいます。(履歴には残る)

<form name="ajaxForm">
<input type="text" name="searchtext"> 
<input type="button" value="検索" onClick="output_result()">
</form>

カスタマイズの要点

データベースの基本的な機能の製作はここまでとします。あとは、検索した後の利用方法を考えてカスタマイズが必要になるでしょう。
商品の販売が目的であれば、cookieを利用したカート機能や注文の送信フォームを付加します。

データベースファイルを作成する

<body onLoad="loadTextFile('sample4.csv')">

データベースファイルを作成したら、ファイル名を上記の部分に入れましょう。
拡張子はcsv以外でも構いませんが、データの構造をCSV形式とし、テキストエンコードは必ずUTF-8にしてください。

検索方法の調節

<input type="radio" name="r_ffs" value="0" checked>テキスト検索 
<input type="radio" name="r_ffs" value="3">価格検索 
<input type="radio" name="r_ffs" value="4">在庫検索 

valueの値が検索フィールドの番号になっています。使用するデータに合わせてメニューを変更する必要があります。
スクリプト内部では変数ffsで受け取り、検索フィールドの指示に利用しています。

並替方法の調節

//対象フィールド
var fbs = 0;
if ((num > 0) && (num < 5)) fbs = ffs;

変数fbsで、並び替えの対象となる項目を決定しています。上記では、検索時の対象「ffs」と同じ項目を並び替えにしています。

for (n=1;n<5;n++) {
	if (n > 1) t += " / ";
	t += list[0][n] + " <a href='javascript:sort_result(" + n + "); output_result2(0)'>昇順</a> <a href='javascript:sort_result(" + n + "); output_result2(1)'>降順</a>";
}

並び替えの指示は上記の部分で行っています。組合せを変更するには、変数fbsの割り当て方と、メニュー表示の内容を合わせて変更する必要があります。

応用事例

グラフ年表スクリプト(人物の生没年一覧と年表をテキストで読み込み、グラフ化して表示)

戻る