Newer
Older
WebSocketSample / quiz / quiz.js
@HIROSE Yuuji HIROSE Yuuji on 19 Aug 9 KB Set server tmp.iekei.org
// 例
function quiz() {
    const QTAG = "quiz2023";
    var name = document.getElementById("name"),
	login = document.getElementById("login"),
	commit = document.getElementById("commit"),
	button = document.getElementById("push"),
	info = document.getElementById("info"),
	qbox = document.getElementById("question"),
	stage = document.getElementById("stage");
    var conn, PORT=9124, server = location.hostname||"localhost";
    server = 'www.koeki-prj.org';
    server = 'tmp.iekei.org'
    var quiz;			// クイズ全体を保持
    var myquiz;			// クライアントモードで受け取ったクイズ情報
    var counthash;		// 現在の問題のクライアント選択状況保持
    var currentQ;		// 現在の問題番号(添字)
    var clientchoice;		// 解答者の選択一覧を保持
    var servermode;		// サーバのとき true
    function replID(id, content) {
	let elm = document.getElementById(id);
	if (elm) elm.textContent = content;
    }
    function disableRadio() {
	for (let r of document.querySelectorAll('input[name="answer"]'))
	    r.disabled = true;
    }
    function resetChecked() {	// すべての選択肢のcheckを外す
        for (let radio of document.querySelectorAll('input[name="answer"]')) {
	    radio.checked = radio.disabled = false; // disabledも解除
	    radio.addEventListener("change", sendChoice, false);
	}
	for (let e of document.querySelectorAll(".bright")) {
	    console.log(e);
	    e.classList.remove("bright");
	}
    }
    function setChoices(quiz) {
	for (let [k, v] of Object.entries(quiz)) {
	    replID(k, v);
	}
    }
    function settoStorage(key, val) {
	let ls = localStorage.getItem(QTAG);
	ls = ls ? JSON.parse(ls) : {}
	ls[key] = val;
	localStorage.setItem(QTAG, JSON.stringify(ls));
	console.log(`localStorage[${key}] <- ${val}`);
    }
    function showStorage(e) {
	let ls = localStorage.getItem(QTAG);
	console.log(`showstorage:ls=${ls}`);
	ls = ls ? JSON.parse(ls) : {}
	let lsc = ls.cleared;
	console.log(`lsc=${JSON.stringify(lsc)}`);
	info.textContent = lsc ? `${Object.keys(lsc).length}問正解` : "not found";
    }
    function resetStorage(e) {
	localStorage.removeItem(QTAG);
	info.textContent = "Reset storage";
    }
    function registerBingo() {	// クライアントモードのみ
	let ls = localStorage.getItem(QTAG);
	ls = ls ? JSON.parse(ls) : {};
	console.log(`ls=${ls}`);
	ls.cleared = ls.cleared||{};
	let q = qbox.textContent;
	console.log(`qbox=${q}, lsq=${ls.cleared}`);
	ls.cleared[q.trim()] = 1;
	localStorage.setItem(QTAG, JSON.stringify(ls));
    }
    function resetParentValues() {
	counthash = {};
	clientchoice = {};
	console.log(`cQ=${currentQ}, qz=${quiz}`);
	//setChoices(quiz[currentQ]);
	sendto({"setquiz": currentQ});
	setChoices(quiz[currentQ]);
	for (let e of document.getElementsByTagName("meter"))
	    e.value = 0; // 解答数メーターを0にする
    }
    function parentSetupStage() {
	let e;
	e = qbox.querySelector("select");
	if (e) e.remove();
	
	// select要素を新規作成して問題文を入れていく
	let select = document.createElement("select"),
	    n = 0;
	while (e = document.querySelector('input[type="radio"]')) {
	    // クイズサーバ画面からはradioボタンを消す
	    e.remove();
	}
	for (let q of quiz) {
	    let opt = document.createElement("option");
	    opt.textContent = q.question;
	    delete q.question; // 親から不要なので消す
	    opt.value = n++;
	    select.appendChild(opt);
	}
	qbox.appendChild(select);
	select.addEventListener("change", (ev) => {
	    currentQ = ev.target.value;
	    resetParentValues();
	});
	console.log(`cQ=${currentQ}, so=${select.options}`);
	select.options[currentQ].selected = true;
    }
    function handleMessage(ev) {
	console.log(`data=${ev.data}`);
	try {
	    var json = JSON.parse(ev.data)
	} catch (err) {
	    // Do nothing...
	    console.log("ERROR!");
	    return;
	}
	if (json.info) {
	    info.textContent = json.info;
	} else if (json.q) {	// サーバから問題集が送られて来た場合
	    quiz = json.q;
	    currentQ = 0;
	    parentSetupStage()
	    resetParentValues();
	} else if (json.setquiz) { // {setquiz: JSON...}
	    myquiz = json.setquiz;
	    console.log(`Got setquiz #${myquiz}`);
	    qbox.textContent = myquiz.question;
	    info.textContent = "問題!";
	    setChoices(myquiz);
	    resetChecked();
	} else if (json.select) { // クライアントからの選択報告
	    // {select: {name: クライアント名, choice: 選択番号}}
	    let n = json.select.name, c = "choice"+json.select.choice;
	    console.log(`cq=${currentQ}, CHOI: ${JSON.stringify(quiz[currentQ])}`);
	    q = quiz[currentQ][c];
	    clientchoice[n] = c
	    console.log(`quiz=${JSON.stringify(quiz)}`)
	    info.textContent = `${n}さんが${q}を選びました。`;
	    console.log(`clichoi=${JSON.stringify(clientchoice)}`);
	    counthash = {};
	    for (let [name, choice] of Object.entries(clientchoice)) {
		counthash[choice] = counthash[choice]||0;
		counthash[choice]++; // 各選択肢を選んだ個数を得る
	    }
	    console.log(`counthash=${JSON.stringify(counthash)}`);
	    for (let [key, val] of Object.entries(quiz[currentQ])) {
		// 各選択肢ごとに集計
		let n = counthash[key]||0,
		    meter = document.getElementById(`N-${key}`);
		if (meter) {
		    meter.value = n;
		    meter.textContent = `${n}人`; // 非グラフィック端末用
		}
	    }
	} else if (json.check) { // サーバからの正解発表
	    let myanswer, rans=json.check.rightanswer;
	    for (let btn of document.getElementsByName("answer")) {
		if (btn.checked) {
		    myanswer = btn.value;
		    break;
		}
	    }
	    let rchoice = document.getElementById(`choice${rans}`)
	    if (rchoice) rchoice.parentNode.parentNode.classList.add("bright");
	    if (myanswer) {
		disableRadio();
		let ra = myquiz[`choice${rans}`]
		info.textContent = `残念...正解は「${ra}」`;
		if (myanswer == rans) {
		    info.textContent = ("正解!");
		    registerBingo();
		} else {
		}
	    } else {
		info.innerText = "(時間切れ)";
	    }
	    info.innerText += `\n${json.check.comment}`;
	} else if (json.result) { // 結果発表!
	    showStorage();
	}
    }
    function LRbtn(e) {
	let dir = parseInt(e.target.value);
	let sel = document.querySelector("select");
	if (!sel) return;
	let last = currentQ;
	currentQ += dir;
	currentQ = Math.max(0, Math.min(sel.options.length-1, currentQ));
	sel.options[currentQ].selected = true;
	if (currentQ != last) resetParentValues();
    }
    function hideLogin() {
	stage.style.display = "block";
	login.style.display = "none";
    }
    function showLogin() {
	stage.style.display = "none";
	login.style.display = "block";
    }
    function initConn(afterinit) {
	try {
	    if (location.hostname) {
		console.log(`Connecting to wss://${server}`);
		conn = new WebSocket('wss://' + server + ':' + '/quiz2023');
	    } else {
		conn = new WebSocket('ws://' + server + ':' + PORT + '/');
	    }
	    conn.onopen = function() {
		let suffix = document.URL.match(/\?(.*)/);
		console.log(`suffix=${suffix}`);
		if (suffix) {
		    servermode = true;
		    sendto({"mode": RegExp.$1});
		    if (!document.getElementById("reset")) {
			let reset = document.createElement("button");
			stage.appendChild(reset);
			reset.textContent = reset.id = "reset";
			reset.addEventListener("click", (e) => {
			    localStorage.removeItem(QTAG);
			});
			let result = document.createElement("button");
			stage.appendChild(result);
			result.textContent = result.id = "RESULT";
			result.addEventListener("click", (e) => {
			    sendto({"result": true});
			});
			document.body.classList.add("server");
		    }
		} else {	// Remove <meter>s on clientmode
		    let m;
		    while (m=document.querySelector("meter"))
			m.remove();
		    button.style.display = "none";
		    document.getElementById("right").style.display =
			document.getElementById("left").style.display = "none";
		}
		button.classList.remove("warn");
		button.textContent = "正解発表";
		if (afterinit) (afterinit)();
	    };		// Nothing special
	    conn.onerror = function(err) {
		alert('WebSocket failure: ' + err)
	    };
	    conn.onmessage = handleMessage;

	    conn.onclose = function(ev) {
		info.textContent = "接続断: 少し待って再接続ボタンを押してください。";
		button.style.display = null;
		button.textContent = "サーバへ再接続";
		button.classList.add("warn");
		conn = null;
	    };
	    info.textContent = "...";
	} catch (err) {
	    alert("Socket Creation Error\n\
Firefoxですか? URLウィンドウに about:config と入れて\n\
Search: 窓に websocket と入れて、\n\
network..websocket.allowInsecureFromHTTP\n\
の行をダブルクリックして true に変えてください。\n" + err);
	}
    }
    function sendto(json) {
	let str = JSON.stringify(json);
	console.log(`sending: ${str}`)
	conn.send(str);
    }
    function setName(e) {
	if (!name.value || name.value=="") {
	    alert("何か名前を入れてください!");
	} else {
	    sendto({name: name.value});
	    if (!servermode) sendto({"getquiz": null});
	    hideLogin();
	}
    }
    function sendChoice(e) {
	let ans = e.target.value;
	console.log(`answer = ${ans}`);
	sendto({"select": ans});
    }
    function check(ev) {
	if (!conn) initConn(setName);
	if (servermode) {
	    sendto({"check": {
		"rightanswer": quiz[currentQ].answer,
		"comment": quiz[currentQ].comment}});
	    settoStorage(currentQ, counthash);
	}
    }
    commit.addEventListener("mousedown", setName, false);
    button.addEventListener("mousedown", check, false);
    let cr = document.getElementById("copyright");
    cr.addEventListener("mouseover", showStorage, false);
    cr.addEventListener("click", resetStorage, false);
    for (let i of ["left", "right"])
	document.getElementById(i).addEventListener("click", LRbtn, false);
    resetChecked();
    initConn();
}
document.addEventListener("DOMContentLoaded", quiz, false);