Статьи

Создание менеджера викторин для jQuery Mobile

Несколько недель назад читатель спросил, разрабатывал ли я когда-нибудь тест для jQuery Mobile . Хотя я этого не делал, я потратил некоторое время на размышления о том, как можно разработать тест, а также о том, как универсальная библиотека может помочь автоматизировать его. Я создал демо, которым я хотел бы поделиться с людьми. Это, безусловно, «Первый черновик» (но эй, это крошка !), Так что не стесняйтесь разорвать его и предложить улучшения.

Я начал с того, что подумал, как можно представить данные викторины. Я решил, что XML или JSON. JSON имеет преимущество быть очень легко работать в JavaScript. Преимущество XML заключается в том, что его действительно легко писать даже для не-разработчиков. В конце дня я остановился на JSON. Моя библиотека может быть обновлена, чтобы обрабатывать оба, хотя. Вот пример теста, который я использовал для демонстрации.

{
	"introduction":"This quiz tests you about foo and goo", 
	"questions":[
		{"question":"Why is the sky blue?", 
		 "answers":["Unicorns","Fairies","Boring Science","Kittens"],
		 "correct":2},
		{"question":"Why are kittens so cute?", 
		 "answers":["Magic","Fur","Meow","More Kittens!"],
		 "correct":3}
	]
}

Схема состоит из необязательного введения и набора вопросов. Каждый вопрос имеет значение вопроса (фактический текст), массив ответов и правильный индекс. Это основано на 0, но я думаю, что имеет смысл быть основанным на 1. Мой дизайн допускает только вопросы с несколькими вариантами ответов с одним ответом, но вы, конечно же, можете делать и правда / ложь.

На стороне jQuery Mobile библиотека используется путем запуска метода execute. Метод execute принимает URL-адрес теста, элемент DOM для его отображения и обратный вызов. Мое приложение jQuery Mobile использует этот файл app.js для обработки этого аспекта:

/* global $,document,console,quizMaster */
$(document).ready(function() {
	
	$(document).on("pageshow", "#quizPage", function() {
		console.log("Page show");
		//initialize the quiz
		quizMaster.execute("q1.json", ".quizdisplay", function(result) {
			console.log("SUCESS CB");
			console.dir(result);	
		});
	});
}); 

Я быстро создал мобильное приложение jQuery с двумя страницами. Первый просто ссылки на страницу викторины.

После загрузки страницы викторины код, который вы видите выше, запускает библиотеку. Вот как тест отображает введение:

И это первый вопрос:

Хорошо, теперь давайте посмотрим на библиотеку.

/* global $,window */
var quizMaster = (function () {
	var name;
	var data;
	var loaded = false;
	var displayDom;
	var successCbAlias;

	function nextHandler(e) {
		e.preventDefault();

		var status = getUserStatus();

		//if we aren't on the intro, then we need to ensure you picked something
		if(status.question >= 0) {
			var checked = $("input[type=radio]:checked", displayDom);
			if(checked.length === 0) {
				//for now, an ugly alert
				window.alert("Please answer the question!");
				return;
			} else {
				status.answers[status.question] = checked.val();	
			}
		} 
		status.question++;
		storeUserStatus(status);
		displayQuiz(successCbAlias);
	}

	function displayQuiz(successCb) {

		//We copy this out so our event can use it later. This feels wrong
		successCbAlias = successCb;
		var current = getQuiz();
		var html;

		if(current.state === "introduction") {
			html = "<h2>Introduction</h2><p>" + current.introduction + "</p>" + nextButton();
			displayDom.html(html).trigger('create');
		} else if(current.state === "inprogress") {
			
			html = "<h2>" + current.question.question + "</h2><form><div data-role='fieldcontain'><fieldset data-role='controlgroup'>";
			for(var i=0; i<current.question.answers.length; i++) {
				html += "<input type='radio' name='quizMasterAnswer' id='quizMasterAnswer_"+i+"' value='"+i+"'/><label for='quizMasterAnswer_"+i+"'>" + current.question.answers[i] + "</label>";
			}
			html += "</fieldset></div></form>" + nextButton();
			displayDom.html(html).trigger('create');
		} else if(current.state === "complete") {
			html = "<h2>Complete!</h2><p>The quiz is now complete. You got "+current.correct+" correct out of "+data.questions.length+".</p>";
			displayDom.html(html).trigger('create');
			removeUserStatus();
			successCb(current);
		}
		
		
		//Remove previous if there...
		//Note - used click since folks will be demoing in the browser, use touchend instead
		displayDom.off("click", ".quizMasterNext", nextHandler);
		//Then restore it
		displayDom.on("click", ".quizMasterNext", nextHandler);
		
	}
	
	function getKey() {
		return "quizMaster_"+name;	
	}
	
	function getQuestion(x) {
		return data.questions[x];	
	}
	
	function getQuiz() {
		//Were we taking the quiz already?
		var status = getUserStatus();
		if(!status) {
			status = {question:-1,answers:[]};
			storeUserStatus(status);
		}
		//If a quiz doesn't have an intro, just go right to the question
		if(status.question === -1 && !data.introduction) {
			status.question = 0;
			storeUserStatus(status);
		}

		var result = {
			currentQuestionNumber:status.question
		};
		
		if(status.question == -1) {
			result.state = "introduction";
			result.introduction = data.introduction;	
		} else if(status.question < data.questions.length) {
			result.state = "inprogress";
			result.question = getQuestion(status.question);	
		} else {
			result.state = "complete";
			result.correct = 0;
			for(var i=0; i < data.questions.length; i++) {
				if(data.questions[i].correct == status.answers[i]) {
					result.correct++;	
				}
			}
		}
		return result;
	}
	
	function getUserStatus() {
		var existing = window.sessionStorage.getItem(getKey());
		if(existing) {
			return JSON.parse(existing);
		} else {
			return null;
		}
	}
	
	function nextButton() {
		return "<a href='' class='quizMasterNext' data-role='button'>Next</a>";	
	}
	
	function removeUserStatus(s) {
		window.sessionStorage.removeItem(getKey());	
	}
	
	function storeUserStatus(s) {
		window.sessionStorage.setItem(getKey(), JSON.stringify(s));
	}
	
	return {
		execute: function( url, dom, cb ) {
			//We cache the ajax load so we can do it only once 
			if(!loaded) {
				
				$.get(url, function(res, code) {
					//Possibly do validation here to ensure basic stuff is present
					name = url;
					data = res;
					displayDom = $(dom);
					//console.dir(res);
					loaded = true;
					displayQuiz(cb);
				});
				
			} else {
				displayQuiz(cb);
			}
		}
	};
}());

Здесь много всего, и я постараюсь объяснить это постепенно. Конец кода — это публичный API, который на данный момент имеет один метод execute. Обратите внимание, как мы определяем, загружен ли тест. Таким образом, мы можем кэшировать JSON и не загружать его после того, как мы извлекли его один раз в запросе.

displayQuiz — основной обработчик для рендеринга теста. Он начинается (игнорировать оператор копирования) с вызова getQuiz. getQuiz обрабатывает взаимодействие с данными викторины, а также с данными пользователя. Я использую sessionStorage, чтобы вспомнить, где вы находитесь в викторине. Это полезно, если вы выходите из теста до его завершения. getQuiz также делает некоторую интеллектуальную обработку состояния. Так, например, если нет вступления, это гарантирует, что вы сразу перейдете к первому вопросу. Он также распознает, когда вы сделали, и проверяет вашу работу.

Back in displayQuiz we use the result of getQuiz to render one of three states — the introduction, the quiz itself, or the completion. By the way, the success callback is used to allow your calling code to record the results to your server via AJAX, or do anything really.

All in all this was fun to write, but as I said, feels very much like a first draft. Want to try it yourself? Hit the demo link below.