JS|閲覧者が選んだクイズの答えを取得する機能の実装|ユーザーが値選択

勘違いしながらドはまりしながら作り上げていく『JavaScript奮闘記』。事始めのJavaScript常識をまとめたページや現在作ろうとしてるクイズの仕様説明はこちら。

ポク太郎です。

本ページの話題は「閲覧者のアクションに従いJavaScriptの分岐先を決定する。」『JavaScript3択クイズを作ろう』中の一機能。

閲覧者が選択した選択肢を取得し、それに対応したプログラムの分岐を司るルーチン。


JavaScriptで閲覧者のアクションを検知

プログラム作成に最も重要なこと「いつ何をトリガとして実行されるのかを理解」。Visual-CやVBAなどのイベント駆動型に慣れるとJavaScriptではどうしてよいか分からなくなる概念。

JavaScriptの場合、HTML上に書かれたボタンやリンク先に実行されるべき関数名を書いておき、プログラムを構築します。

<a href="javascript:quiz_answer(1)">選択肢1</a>
<a href="javascript:quiz_answer(2)">選択肢2</a>
<a href="javascript:quiz_answer(3)">選択肢3</a>

上記は閲覧者が選択肢1をクリックした場合は関数quiz_answer()に引数1を渡す、選択肢2なら引数2選択肢3なら引数3て意味。

JavaScriptのソース内でなくHTML内に遷移先が書かれる構成なので、プログラムの流れが分からなくなる主要因。なので要注意。

閲覧者が押すボタンとクイズを実際に表示する

前回、別ページBのクイズデータから実際に出題する問題を選び出す部分まで作りましたのでそのデータを元に画面に表示します。

クイズを表示するHTMLの表

クイズの問題と選択肢の表示フォーマットは以下のように。HTMLの表内に表示する仕様とします。

問題文表示
選択肢1表示選択肢2表示選択肢3表示
1選択ボタンリンク2選択ボタンリンク3選択ボタンリンク

上記を表示するHTMLソースは、

<table style="border-style:solid;border-width:1px;table-layout: fixed;width: 100%;">
<tbody>
<tr>
<td style="text-align:left;" colspan="3">問題文表示</td>
</tr>

<tr>
<td style="text-align:left;">選択肢1表示</td>
<td style="text-align:left;">選択肢2表示</td>
<td style="text-align:left;">選択肢3表示</td>
</tr>

<tr>
<td style="text-align:center;">1選択ボタンリンク</td>
<td style="text-align:center;">2選択ボタンリンク</td>
<td style="text-align:center;">3選択ボタンリンク</td>
</tr>
</tbody></table>

このHTMLを吐き出すJavaScriptのソースを作成します。

表を出力するJavaScriptプログラム

グローバル配列qx[][]に実際に出題するクイズデータが代入済。先回決めたデータのフォーマットは、

クイズNo,カテゴリ,シャッフルフラグ,問題文,選択肢1,選択肢2,選択肢3,正解の解説文
シャッフルフラグとは選択肢の順番を並び替えて出題するかどうか、また選択肢中の正解には文末に○を付けておく仕様。

上記データから問題の表を出力するプログラムが以下。変数se[]sb[]等に必要情報を作り、それをHTMLとして組み合わせます。

function quiz_display(q_monme) {
//選択肢のHTMLソースを作成する
	var s = '';
	var sq = '';//問題文
	var se = [];//各選択肢
	var sb = [];//各選択肢リンク

			sq = qx[q_monme-1][3];
		for (var i=0;i<q_sel;i++) {
			se[i] = (i+1).toString() + ':<br />' + qx[q_monme-1][4+i].replace('○','');
			sb[i] = '<a href="javascript:quiz_answer(' + (i+1).toString() + ')">選択肢' + (i+1).toString() + '</a>';
		}

			s = '<table style="border-style:solid;border-width:1px;table-layout:fixed;width:100%;"><tbody><tr><td style="text-align:left;" colspan="3">【' + q_monme + '問目】' + sq + 
'</td></tr><tr><td style="text-align:left;">' + se[0] + 
'</td><td style="text-align:left;">' + se[1] + 
'</td><td style="text-align:left;">' + se[2] + 
'</td></tr><tr><td style="text-align:center;">' + sb[0] + 
'</td><td style="text-align:center;">' + sb[1] + 
'</td><td style="text-align:center;">' + sb[2] + 
'</td></tr></tbody></table>';

			document.getElementById("debug").innerHTML = s;
	//ここでプログラムは一時停止。閲覧者の選択動作により→quiz_answer()へ
}

ソースコード説明

閲覧者に出題するクイズを表示する関数quiz_display()。関数の引数はn問目を示す変数q_monme。対応するクイズデータはqx[q_monme-1]

変数se[]sb[]に必要情報を作ります。

変数se[]は選択肢としての表示文字列。正解の選択肢文末を○としておく仕様としたので、.replace(‘○’,”)としてデータ内の○を削除しています。

変数sb[]は閲覧者がアクションを起こすためのリンク部分作成。上で書いた閲覧者の押したリンクをquiz_answer()関数に関連付けます。

<a href=”javascript:quiz_answer(1)”>選択肢1</a>

上記変数を利用して、表のHTML書式を変数sに代入し表示します。

閲覧者のアクションを受けて正誤判定する

上記関数でHTML内に問題文と選択肢、閲覧者の回答手段を表示させました。その後、閲覧者が回答のリンクを押すことでJavaScript内の関数quiz_answer()へと処理が移ります。

準備した関数とフロチャート
1quiz_display()上で説明した問題文の表を表示する部分。
閲覧者がリンクをクリックすることで2番へ。
2quiz_answer()正誤判定と閲覧者の成績記録(変数an[]部分。
1問ごとの正誤表示を設定する変数mode_tudoに従い、3番、4番へ。
3quiz_rplay()正誤の判定を1.8秒間表示する部分。
時間経過後、4番へ。
4quiz_next()クイズの終了判定部分。
全問回答したなら5番へ、違うなら問題番号を保持する変数countを+1して1番に戻る。
5quiz_result()全問題の成績と解説表示部分。
ここが実行されたらプログラム終了。

閲覧者の選んだ回答を正誤判定

以下が上表2、3、4番の関数のソースコード。

function quiz_answer(select) {
//閲覧者の回答結果を採点
	var seikai = '';//正解の解説
	var n_seikai = 0;//正解の番号

	//クイズのフォーマット
	//クイズNo,カテゴリ,シャッフルフラグ,問題文,選択肢1,選択肢2,選択肢3,正解の解説文

					seikai = qx[count-1][3+q_sel+1];

		for (var i=4;i<=3+q_sel+1;i++) {
			if( qx[count-1][i].indexOf('○') != -1 ){
					n_seikai = i - q_sel;
					break;
			}
		}
			if( select == n_seikai ){
					an[count-1] = 1;
			} else {
					an[count-1] = 0;
			}

			document.getElementById("debug").innerHTML += n_seikai + seikai;

			if( mode_tudo == 0 ){
					quiz_next();
			} else {
					quiz_rplay(an[count-1]);
			}
}

function quiz_rplay(r) {
//回答結果を都度表示
		if( r == 0 ){
			document.getElementById("debug").innerHTML = '<div style="text-align:center;font-size:50px;">ブー!</div>';
		} else {
			document.getElementById("debug").innerHTML = '<div style="text-align:center;font-size:50px;">ピンポーン!</div>';
		}
			               setTimeout('quiz_next()', 1800);
}


function quiz_next() {
//終了判定と次の処理
					count = count + 1;
			if( count > ntest ){
					quiz_result();
			} else {
					quiz_display(count);
			}
}

ソースコード説明

選択肢データ中に’○’があるものを探し、その番号を変数n_seikaiに代入します。変数q_selは選択肢の数で現在3。

正解なら1、不正解なら0を変数an[]に記録していきます。

変数mode_tudoは1問回答ごとに正誤判定を表示するかどうかを決めるグローバル変数で1ならする、0ならしない仕様。その判定に従い、次に呼ばれる関数を選択しています。

関数quiz_rplay()は1問回答ごとの正誤判定表示部分。変数an[]の値を引数として渡します。正解不正解を表示し、1.8秒間の時間待ち後に関数quiz_next()へ処理を移します。

setTimeout(‘関数名’, 待ち時間[ms])

関数quiz_next()では出題数が設定された変数ntestと現在の問題数を比較し終了判定。途中なら再度quiz_display()を呼び出し、違うなら結果表示の関数quiz_result()へ処理を移します。

閲覧者の成績とクイズの解説を表示

閲覧者が全問回答を終えたらこの関数quiz_result()が呼び出されます。

閲覧者の成績記録である変数an[]と各問題を表内に表示します。JavaScript内のコードは主に表のHTML書式を作成する仕事が主です。

成績と解説の表示用のHTML作成

関数quiz_result()と終了時に再チャレンジのためのリロード機能を実現するための関数quiz_reload()

function quiz_result() {
//結果表示
	var tokuten = 0;//得点
	var message = '';
	var message2 = '';
	var kaisetu = '';
	var t = [];
	var mondai = [];
	var tmp = '';

		for (var i=0;i<an.length;i++) {
					tokuten = tokuten + an[i];
		}
			message = '<div style="text-align:center;font-size:30px;">あなたの得点は<br />' + tokuten + '点 / ' + ntest +'問中<br />でした。</div>';
			message2 = '<div style="text-align:center;font-size:30px;"><a href="javascript:quiz_reload(0)">再チャレンジする</a></div>';

	t[0] = '<table style="border-style:solid;border-width:2px;width: 100%;"><tbody><tr><td style="text-align:left;" colspan="2">';//N問目:カテゴリ
	t[1] = '</td><td style="text-align:left;" colspan="2">あなたの正誤:';
	t[2] = '</td></tr><tr><td style="width:3%;" rowspan="3"></td><td align="left" colspan="3">問題:';
	t[3] = '</td></tr><tr><td style="text-align:left;width:32%;">1: ';//択1
	t[4] = '</td><td style="text-align:left;width:32%;">2: ';//択2
	t[5] = '</td><td style="text-align:left;width:32%;">3: ';//択3
	t[6] = '</td></tr><tr><td style="text-align:left;" colspan="3">';//解説
	t[7] = '</td></tr></tbody></table>';

		for (i=0;i<ntest;i++) {
				if( an[i] == 0 ){
						tmp = 'ブー!';
				} else {
						tmp = 'ピンポーン!';
				}
					mondai[i] = t[0] + (i+1).toString() + '問目:' + catego +
						t[1] + tmp +
						t[2] + qx[i][3] +
						t[3] + qx[i][4].replace('○','') +
						t[4] + qx[i][5].replace('○','') +
						t[5] + qx[i][6].replace('○','') +
						t[6] + qx[i][7] + t[7];
		}

		for (i=0;i<ntest;i++) {
						kaisetu = kaisetu + '<br />' + mondai[i];
		}

			document.getElementById("debug").innerHTML = message + '<br />' + message2 + '<br />' + kaisetu;
}


function quiz_reload(num) {
//初期化しリロード
		window.scrollTo( 0, 0 );//トップに移動
		document.getElementById("debug").innerHTML = "";
		qx=[];
		an=[];

		location.reload(true);
}

ソースコード説明

まず「あなたの得点は○点でした」と表示するため、変数an[]を全要素に渡り足し算し得点を計算し表示。

上ではそのメッセージ内部には、選択肢のリンクと同様に、

<a href=”javascript:quiz_reload(0)”>再チャレンジする</a>

関数quiz_reload()を呼び出せるようにしてあります。

HTMLの表を作るのが非常に煩雑なので、配列変数t[]にそれぞれのHTMLタグを代入してしまいます。それを利用し各問題の成績それぞれの表を変数mondai[]に代入。

t[1] + tmp +…と次の行へ移動して書いてるのは文末記号;を必須とするJavaScriptならでは。無視される改行を利用しできるだけ見やすく記述。

出来上がった成績表示部を表示しプログラム終了。

最後に、関数quiz_reload()では、スクロール位置を最上部に戻し、画面消し、変数を初期化しリロードしています。

location.reload(true);

出来上がったクイズとソースコード全体

出来上がったクイズプログラム

出来上がったクイズプログラムがこちら。

ここから

ここまで表示領域。

クイズプログラム全ソースコード

全体のソースコードは、

クイズのデータベースとなる別ページBHTMLソース
<html>
<head>
<meta name="robots" content="noindex,noarchive,nofollow">
</head>

<body>

<!-- ここに貼る -->
<div style="visibility:hidden;">
<div class="qdt">9000,足し算,r,3+5=?,8○,9,10,正解は「8」です。</div>
<div class="qdt">9001,足し算,r,4+5=?,8,9○,10,正解は「9」です。</div>
<div class="qdt">9002,足し算,r,3+9=?,12○,15,10,正解は「12」です。</div>
<div class="qdt">9003,足し算,r,2+8=?,8,9,10○,正解は「10」です。</div>
<div class="qdt">9004,引き算,r,2-5=?,-3○,-5,7,正解は「-3」です。</div>
<div class="qdt">9005,引き算,r,11-4=?,7○,9,10,正解は「7」です。</div>
<div class="qdt">9006,引き算,r,9-2=?,7○,6,11,正解は「7」です。</div>
<div class="qdt">9007,引き算,r,8-5=?,3○,2,13,正解は「3」です。</div>
<div class="qdt">9008,掛け算,r,2x5=?,10○,12,7,正解は「10」です。</div>
<div class="qdt">9009,掛け算,r,11x4=?,44○,42,7,正解は「44」です。</div>
<div class="qdt">9010,掛け算,r,4x4=?,16○,20,8,正解は「16」です。</div>
<div class="qdt">9011,掛け算,r,3x4=?,12○,10,7,正解は「12」です。</div>
<div class="qdt">9012,割り算,r,10/5=?,2○,3,4,正解は「2」です。</div>
<div class="qdt">9013,割り算,r,9/3=?,3○,4,5,正解は「3」です。</div>
<div class="qdt">9014,割り算,r,3/2=?,1.5○,2,1,正解は「1.5」です。</div>
<div class="qdt">9015,割り算,r,4/2=?,2○,1,4,正解は「2」です。</div>
<div class="qdt">9016,慣用句,r,○○を漢字で埋めなさい。豚に○○。,真珠○,念仏,小判,正解は「真珠」です。</div>
<div class="qdt">9017,慣用句,r,○○を漢字で埋めなさい。人生万事塞翁が○,馬○,豚,猿,正解は「馬」です。</div>
<div class="qdt">9018,慣用句,r,○○を漢字で埋めなさい。猿吉は○でも踏んどれ。,麦○,米,肉,正解は「麦」です。</div>
<div class="qdt">9019,慣用句,r,○○を漢字で埋めなさい。パンツトラナイデネ!○○トコナイデネ!,二度○,再度,既,正解は「二度」です。</div>
</div>
</body></html>
実際にクイズを設置するページのHTMLソース
<div id="debug"></div>
JavaScriptソース
ソース内で「https://poku.blog/js-test」とある部分を上記別ページBのURLに変更する必要があります。
var qdt=[];//クイズのデータ
var qdt2=[];//クイズのデータ2次元配列化

var mode_tudo=1;//1:1問づつ正誤表示
var catego='掛け算';

var ntest=3;//出題数
var count=0;//現在の問題番号
var q_sel=3;//選択肢の数

var qx=[];//qx[]出題された問題
var an=[];//an[]正解したら1違うなら0

document.getElementById("debug").innerHTML = '';
var obj = new XMLHttpRequest();
obj.open('GET','https://poku.blog/js-test',true);//true:非同期通信
obj.onreadystatechange = function(){

	if (obj.readyState === 4 && obj.status === 200){
		var str = obj.responseText; //読み込んだHTMLを変数に代入
//----処理
		qdt = str.match(/<div class="qdt">.+?<\/div>/g);

			document.getElementById("debug").style.display = '';

		quiz_make();

//----処理
	}

	};
	obj.send(null); //リクエストの送信

function rand( min, max ) {
//範囲選択可能な乱数
	var random = Math.floor( Math.random() * (max + 1 - min) ) + min;
	return random;
}

function quiz_make() {
//クイズデータの作成
	var tmp=[];//2次元配列作成用
	var sel=[];//既に選択済の番号記憶

	//2次元配列qdt2にデータ入れ直し
	for (var i=0;i<qdt.length;i++){
			tmp=qdt[i].split(',');
			qdt2[i]=[];//JavaScript常識、配列の階層ごとに初期化要
		for (var j=0;j<tmp.length;j++){
			qdt2[i][j]=tmp[j];
		}
	}

							var n;
							var find;

	//無限ループに陥らないよう条件に合うcategoの数>出題数を先に確認する
							var kazu = 0;
							var jd;
	for (var i=0;i<qdt.length;i++){
			if (qdt2[i][1] == catego){
							kazu = kazu + 1;
			}
	}
	if (kazu >= ntest){
							jd = true;
	} else {
							jd = false;
	}

	if (jd == false){
				document.getElementById("debug").innerHTML += kazu + 'エラー:条件に合うクイズデータが出題数より少なく無限ループに陥ります。';
	} else {
		for(i=0;i<ntest;i++){
			while(0==0){
							find=1;
							n=9000+rand(0,qdt.length-1);
	//	document.getElementById("debug").innerHTML += 'n=' + n;
				if (qdt2[n-9000][1] == catego){
							find=0;
					for (i=0;i<sel.length;i++){
						if (sel[i] == n){
							find=1;
							break;
						}
					}
				}
				if(find == 0){
							sel.push(n);
							break;
				}
			}
		}
	}

				tmp=[];//2次元配列作成用
	for (i=0;i<sel.length;i++){
				n=Number(sel[i]-9000);
				tmp=qdt[n].split(',');
				qx[i]=[];//JavaScript常識、配列の階層ごとに初期化要
		for (j=0;j<tmp.length;j++){
				qx[i][j]=tmp[j];
		}
	}
				quiz_shuffle();
				count = 1;
				quiz_display(1);

	//id="debug"タグに選び出したクイズデータqxを記述する。
	for (i=0;i<qx.length;i++){
	//	document.getElementById("debug").innerHTML += '選択したクイズNo.:' + qx[i][0] + ' カテゴリ:' + qx[i][1] + ' 問題文:' + qx[i][3] + '<br />';
	}
}

function quiz_shuffle() {
//シャッフルフラグの対応
	//クイズのフォーマット
	//クイズNo,カテゴリ,シャッフルフラグ,問題文,選択肢1,選択肢2,選択肢3,正解の解説文

						var tmp;
						var a,b;
	for (var i=0;i<qx.length;i++) {
		if(qx[i][2] == 'r'){
			for (var j=0;j<100;j++) {
			//100回入替を行う
						a = rand(4,4+q_sel-1);
						b = rand(4,4+q_sel-1);
						tmp = qx[i][a];
						qx[i][a] = qx[i][b];
						qx[i][b] = tmp;
			}
		}
	}

	//id="debug"タグに選び出したクイズデータqxを記述する。
	for (i=0;i<qx.length;i++){
		document.getElementById("debug").innerHTML += '選択したクイズNo.:' + qx[i][0] + ' 選択肢1:' + qx[i][4] + ' 選択肢2:' + qx[i][5] + ' 選択肢3:' + qx[i][6] + '<br />';
	}
}

function quiz_display(q_monme) {
//選択肢のHTMLソースを作成する
	var s = '';
	var sq = '';//問題文
	var se = [];//各選択肢
	var sb = [];//各選択肢リンク

			sq = qx[q_monme-1][3];
		for (var i=0;i<q_sel;i++) {
			se[i] = (i+1).toString() + ':<br />' + qx[q_monme-1][4+i].replace('○','');
			sb[i] = '<a href="javascript:quiz_answer(' + (i+1).toString() + ')">選択肢' + (i+1).toString() + '</a>';
		}

			s = '<table style="border-style:solid;border-width:1px;table-layout:fixed;width:100%;"><tbody><tr><td style="text-align:left;" colspan="3">【' + q_monme + '問目】' + sq + 
'</td></tr><tr><td style="text-align:left;">' + se[0] + 
'</td><td style="text-align:left;">' + se[1] + 
'</td><td style="text-align:left;">' + se[2] + 
'</td></tr><tr><td style="text-align:center;">' + sb[0] + 
'</td><td style="text-align:center;">' + sb[1] + 
'</td><td style="text-align:center;">' + sb[2] + 
'</td></tr></tbody></table>';

			document.getElementById("debug").innerHTML = s;
	//ここでプログラムは一時停止。閲覧者の選択動作により→quiz_answer()へ
}

function quiz_answer(select) {
//閲覧者の回答結果を採点
	var n_seikai = 0;//正解の番号

	//クイズのフォーマット
	//クイズNo,カテゴリ,シャッフルフラグ,問題文,選択肢1,選択肢2,選択肢3,正解の解説文

		for (var i=4;i<=3+q_sel+1;i++) {
			if( qx[count-1][i].indexOf('○') != -1 ){
					n_seikai = i - q_sel;
					break;
			}
		}
			if( select == n_seikai ){
					an[count-1] = 1;
			} else {
					an[count-1] = 0;
			}

			if( mode_tudo == 0 ){
					quiz_next();
			} else {
					quiz_rplay(an[count-1]);
			}
}

function quiz_rplay(r) {
//回答結果を都度表示
		if( r == 0 ){
			document.getElementById("debug").innerHTML = '<div style="text-align:center;font-size:50px;">ブー!</div>';
		} else {
			document.getElementById("debug").innerHTML = '<div style="text-align:center;font-size:50px;">ピンポーン!</div>';
		}
			               setTimeout('quiz_next()', 1800);
}

function quiz_next() {
//終了判定と次の処理
					count = count + 1;
			if( count > ntest ){
					quiz_result();
			} else {
					quiz_display(count);
			}
}

function quiz_result() {
//結果表示
	var tokuten = 0;//得点
	var message = '';
	var message2 = '';
	var kaisetu = '';
	var t = [];
	var mondai = [];
	var tmp = '';

		for (var i=0;i<an.length;i++) {
					tokuten = tokuten + an[i];
		}
			message = '<div style="text-align:center;font-size:30px;">あなたの得点は<br />' + tokuten + '点 / ' + ntest +'問中<br />でした。</div>';
			message2 = '<div style="text-align:center;font-size:30px;"><a href="javascript:quiz_reload(0)">再チャレンジする</a></div>';

	t[0] = '<table style="border-style:solid;border-width:2px;width: 100%;"><tbody><tr><td style="text-align:left;" colspan="2">';//N問目:カテゴリ
	t[1] = '</td><td style="text-align:left;" colspan="2">あなたの正誤:';
	t[2] = '</td></tr><tr><td style="width:3%;" rowspan="3"></td><td align="left" colspan="3">問題:';
	t[3] = '</td></tr><tr><td style="text-align:left;width:32%;">1: ';//択1
	t[4] = '</td><td style="text-align:left;width:32%;">2: ';//択2
	t[5] = '</td><td style="text-align:left;width:32%;">3: ';//択3
	t[6] = '</td></tr><tr><td style="text-align:left;" colspan="3">';//解説
	t[7] = '</td></tr></tbody></table>';

		for (i=0;i<ntest;i++) {
				if( an[i] == 0 ){
						tmp = 'ブー!';
				} else {
						tmp = 'ピンポーン!';
				}
					mondai[i] = t[0] + (i+1).toString() + '問目:' + catego +
						t[1] + tmp +
						t[2] + qx[i][3] +
						t[3] + qx[i][4].replace('○','') +
						t[4] + qx[i][5].replace('○','') +
						t[5] + qx[i][6].replace('○','') +
						t[6] + qx[i][7] + t[7];
		}

		for (i=0;i<ntest;i++) {
						kaisetu = kaisetu + mondai[i];
		}

			document.getElementById("debug").innerHTML = message + kaisetu + message2;
}

function quiz_reload(num) {
//初期化しリロード
		window.scrollTo( 0, 0 );//トップに移動
		document.getElementById("debug").innerHTML = "";
		qx=[];
		an=[];

		location.reload(true);
}

一応完成しました。

ただ現在のはやりっぱなしのクイズなので、結果をどこかに保存して高得点者を記録する機能などもその内作ってみたいと思います。

JavaScript3択クイズを作ろう
Step1
仕様・必要機能
Step2
別ページのHTML取得
Step3
出題用2次元配列へ
Step4
閲覧者の回答検出・判定

コメント

タイトルとURLをコピーしました