Skip to content

Instantly share code, notes, and snippets.

@ottograjeda
Last active August 5, 2019 18:45
Show Gist options
  • Save ottograjeda/b5b182c00f70861ef1e630dda3d64c4d to your computer and use it in GitHub Desktop.
Save ottograjeda/b5b182c00f70861ef1e630dda3d64c4d to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<!-- ************************************************************* -->
<!--!
Filename: gist.3a = quizBank.html
Built for performance; is responsive & small in file size.
Above means: all comments+whitespace removed. CS/JS minified.
Toolbox = Gulp, Plain JavaScript & more. Hosted on Google Cloud.
Copyright (c) 2018-2019 Otto Grajeda. All rights reserved.
Contact info: ottograjeda@gmail.com. See additional disclaimers below.
NOTE 1: Code is NOT Open Source or Production Ready. It is working sample code.
NOTE 2: For support or code questions search keywords @ Google or Stack Overflow.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- - - - - - - - - - - - - - -
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- - - - - - - - - - - - - - -
-->
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/css/pure.min.css" rel="stylesheet" media="screen" type="text/css">
<title></title>
</head>
<body>
<div class="container-fluid">
<div id="quiz"></div>
</div>
<script src="/js/zepto.min.js"></script>
<script src="/js/tabletop.min.js"></script>
<script>
(function($) {
function score (nCorrect, nQuestions) {
var answersWord = nCorrect === 1 ? 'answer' : 'answers';
var html = '<div id=\'summaryBox\'>' +
'<div id=\'summaryTop\'>Select answer</div>' +
'<div id=\'summaryScore\'><p>Current score: <span class="correct-answers">' + nCorrect + '</span> of ' + nQuestions + ' correct</div></div>' +
'<div id=\'buttonNext\'><p><button style="">Next</button></div>' +
'</div>';
return html;
}
function scores(nQuestions) {
var ret = [];
for (var i = 0; i <= nQuestions; i++) {
ret.push(score(i, nQuestions));
}
return ret;
}
$.quiz = function(quiz_data, results_data, options) {
var container_elem;
var self;
var cover;
var cheater_answer_tracking = [];
var answer_tracking = [];
var how_you_did_element;
var right_answers = 0;
var oneByOne = false;
var onFinish;
var quiz = {
defaulting_behavior_on : true,
defaulting_flag : '!default',
container : 'quiz',
not_finished_html : undefined,
cheating : false,
possible_display_elements : [
{
name : 'backgroundimage',
finder: function(container) {
return container.find('.' + this.name);
},
create_element : function(slide) {
if (!slide[this.name]) {return '';}
return $('<div class="' + this.name + '" style="background-image: url(\'' +
slide[this.name] + '\'); height: 100%; width: 100%;position:absolute;z-index: -1"></div>');
}
},
{
name : 'topimage',
finder: function(container) {
return container.find('.' + this.name);
},
create_element : function(slide) {
if (!slide[this.name]) {return '';}
return $('<img src="' + slide[this.name] + '" class="img-responsive' + this.name + '"></img>');
}
},
{
name : 'title',
finder: function(container) {
return container.find('.' + this.name);
},
create_element : function(slide) {
if (!slide[this.name]) {return '';}
return $('<h3 class="' + this.name + '">' + slide[this.name] + '</h3>' );
}
},
{
name : 'middleimage',
finder: function(container) {
return container.find('.' + this.name);
},
create_element : function(slide) {
if (!slide[this.name]) {return '';}
return $('<img src="' + slide[this.name] + '" class="img-responsive ' + this.name + '"></img>');
}
},
{
name : 'subhed',
finder: function(container) {
return container.find('.' + this.name);
},
create_element : function(slide) {
if (!slide[this.name]) {return '';}
return $('<h2 class="' + this.name + '">' + slide[this.name] + '</h2>');
}
},
{
name : 'text',
finder: function(container) {
return container.find('.' + this.name);
},
create_element : function(slide) {
if (!slide[this.name]) {return '';}
return $('<p class="' + this.name + '">' + slide[this.name] + '</p>');
}
},
{
name : 'bottomimage',
finder: function(container) {
return container.find('.' + this.name);
},
create_element : function(slide) {
if (!slide[this.name]) {return '';}
return $('<img src="' + slide[this.name] + '" class="img-responsive ' + this.name + '"></img>');
}
}
],
init : function(quiz_data, results_data, options) {
self = this;
if (options) {
for ( var option in options ) {
self[option] = options[option];
}
}
if (typeof(quiz_data) === 'string') {
self.load_from_google_spreadsheet(quiz_data);
} else {
if (!results_data) {
results_data = scores(quiz_data.length);
}
self.init_data(quiz_data, results_data);
}
return self;
},
init_data: function(quiz_data, results_data) {
self.quiz_data = quiz_data;
self.results_data = results_data;
self.calculate_aspectratios(quiz_data);
self.create_cover();
for ( var i = 0; i < self.quiz_data.length; i++ ) {
self.append_question(i);
}
self.append_how_you_did_section();
self.update_how_you_did_element();
},
append_how_you_did_section: function() {
how_you_did_element = $('<p class="how_you_did"></p>');
cover.append(how_you_did_element);
},
setHowYouDidTriggers : function () {
$('.how_you_did button').on('click', function () {
$('.question_container:not(.hidden)').addClass('hidden').next().removeClass('hidden');
if ($('.question_container:not(.hidden)').length == 0) {
var elem1 = document.getElementById("summaryTop");
elem1.remove();
var elem2 = document.getElementById("summaryScore");
elem2.remove();
var elem3 = document.getElementById("buttonNext");
elem3.remove();
var target = document.querySelector('#summaryBox');
var div = document.createElement('div');
div.innerHTML = '<div id=\'finalScore\' style="color:white;><p>FINAL score: <span class="correct-answers">' + self.right_answers + '</span> of ' + self.quiz_data.length + ' correct</div></div>';
target.parentNode.insertBefore( div, target.nextSibling );
}
});
},
cleanCells : function (cells) {
for (k in cells) {
if (cells[k] == " ") {
cells[k] = "";
}
}
return cells;
},
load_from_google_spreadsheet: function(spreadsheet_id) {
Tabletop.init({
proxy: this.proxy ? this.proxy : undefined,
key: spreadsheet_id,
prettyColumnNames: false,
callback: function(data) {
var quiz_data = self.make_quiz_data_from_spreadsheet_data(data);
var results_data = self.make_results_data_from_spreadsheet_data(data, quiz_data);
self.init_data(quiz_data, results_data);
}
});
},
calculate_aspectratios: function(data) {
for (var i = 0; i < data.length; i++) {
var row = data[i];
for (var k = 0; k < row.possible_answers.length; k++) {
var answer = row.possible_answers[k];
self.find_aspectratio_for_each_type_of_video_embed(answer);
}
self.find_aspectratio_for_each_type_of_video_embed(row.question);
}
},
find_aspectratio_for_each_type_of_video_embed : function(slide) {
for (var i = 0; i < self.possible_display_elements.length; i++ ) {
var display = self.possible_display_elements[i];
if ( display.needs_aspect_ratio && slide[display.name] ) {
slide[display.name + 'aspectratio'] =
self.find_aspectratio(slide[display.name]);
}
}
},
find_aspectratio: function(videoembed) {
var height = videoembed.match(/height="\d+"/);
if (!height || !height[0]) {
return '';
}
height = parseInt(height[0].replace(/height="/, '').replace(/"/, ''), 10);
var width = videoembed.match(/width="\d+"/);
if (!width || !width[0]) {
return '';
}
width = parseInt(width[0].replace(/width="/, '').replace(/"/, ''), 10);
return (height / width)*100;
},
pull_answer_value_from_spreadsheet : function(row, value, wrong_number, correct) {
correct = correct ? 'right' : 'wrong';
if (row[correct + wrong_number + value] && row[correct + wrong_number + value] !== self.defaulting_flag) {
return (row[correct + wrong_number + value]);
}
if ((self.defaulting_behavior_on && row[correct + wrong_number + value] !== self.defaulting_flag) ||
(!self.defaulting_behavior_on && row[correct + wrong_number + value] === self.defaulting_flag)
) {
return (row[correct + value] && row[correct + value] !== self.defaulting_flag ? row[correct + value] :
(row['answer' + value] && row['answer' + value] !== self.defaulting_flag ?
row['answer' + value] : row['question' + value] )
);
} else {
return '';
}
},
get_possible_answers : function(row, is_correct) {
var possible_answers = [];
var right_or_wrong = (is_correct ? 'right' : 'wrong');
if (row[right_or_wrong]) {
possible_answers.push(self.make_possible_answer(row, '', is_correct));
}
for (var i = 0; i < 10; i++ ) {
if (row[right_or_wrong + i]) {
possible_answers.push(self.make_possible_answer(row, i, is_correct));
}
}
return possible_answers;
},
make_possible_answer: function(row, row_number, is_correct) {
var right_or_wrong = (is_correct ? 'right' : 'wrong');
var answer = {
answer: row[right_or_wrong + row_number],
correct: is_correct
};
for (var i = 0; i < self.possible_display_elements.length; i++ ) {
var display_element = self.possible_display_elements[i].name;
answer[display_element] = self.pull_answer_value_from_spreadsheet(
row, display_element, row_number, is_correct
);
}
return answer;
},
make_quiz_data_from_spreadsheet_data: function(tabletop) {
var i, j, sheetName, data;
var quiz = [];
for (sheetName in tabletop) {
if (tabletop.hasOwnProperty(sheetName) && sheetName !== 'Results') {
break;
}
}
data = tabletop['<a_test_file>'].elements;
for (i = 0; i < data.length; i++) {
var row = row = self.cleanCells(data[i]);
var possible_wrong_answers = self.get_possible_answers(row, false);
var possible_right_answers = self.get_possible_answers(row, true);
var right_answer_placement = [];
for (j = 0; j < possible_right_answers.length; j++) {
right_answer_placement.push(
Math.round(Math.random() * possible_wrong_answers.length)
);
}
right_answer_placement.sort();
var possible_answers= [];
var right_answers_placed = 0;
for (j = 0; j <= possible_wrong_answers.length; j++) {
while (j === right_answer_placement[right_answers_placed]) {
possible_answers.push(possible_right_answers[right_answers_placed]);
right_answers_placed++;
}
if (j === possible_wrong_answers.length) {
continue;
}
possible_answers.push(possible_wrong_answers[j]);
}
var question = {
question : {
},
possible_answers : possible_answers,
rowNumber : row.rowNumber - 1
};
for (j = 0; j < self.possible_display_elements.length; j++) {
var display_value = self.possible_display_elements[j].name;
question.question[display_value] = row['question' + display_value];
}
quiz.push(question);
}
return quiz;
},
make_results_data_from_spreadsheet_data: function(tabletop, quiz_data) {
var ret = scores(quiz_data.length);
var data = tabletop['Results'] ? tabletop['Results'].elements : [];
for (var i = 0; i < data.length; i++) {
var index = data[i].numberofrightanswers;
if (index) { index = parseInt(index, 10); }
if (!isNaN(index)) {
if (!ret[index]) {
console.log("invalid" + index);
} else {
ret[index] = data[i].html;
}
}
}
return ret;
},
append_question : function(question_index) {
var question_data = self.quiz_data[question_index],
hidden = "";
if (question_index != 0 && self.oneByOne) {
hidden = 'hidden';
}
var question_container = $('<div class="question_container '+ hidden +' row-fluid question_' + question_index + '"></div>');
var qNumber = question_index+1;
question_container.append('<h3 class=\'title\' style="padding:10px"> Question ' +qNumber+ '</h3><hr>');
question_container.append( self.build_question_element_from_row(question_data) );
question_container.append( self.build_possible_answer_elements_from_row(question_data, question_index) );
container_elem.append(question_container);
},
build_question_element_from_row: function(row) {
var question_container = $('<div class="question show" style="overflow: hidden; position: relative; padding: 10px; width:95%;"></div>');
for (var i = 0; i < self.possible_display_elements.length; i++) {
question_container.append(self.possible_display_elements[i].create_element(row.question)
);
}
return question_container;
},
build_possible_answer_elements_from_row : function(question, question_index) {
var answers_container = $('<ul id=possibleAnswers class="list-unstyled possible_answers possible_answers_' + question_index + '"></ul>');
function bindClick(question_index, answer_index, possible_answer) {
possible_answer.bind('click', function() {
var was_correct = self.quiz_data[question_index].possible_answers[answer_index].correct;
var qNumber = question_index+1;
var qStatus = was_correct;
var qTitle = JSON.stringify(self.quiz_data[question_index].question.title);
google.script.run.qDetails(qNumber, qTitle, qStatus);
answers_container.find('.selected').removeClass('selected');
$(this).addClass('selected');
$(this).removeClass('possible_answer');
answers_container
.find('.answer_' + answer_index)
.addClass(
was_correct ? 'correct-answer' : 'wrong-answer'
);
var elemAnswers = document.getElementById("possibleAnswers");
elemAnswers.remove();
cheater_answer_tracking[question_index] = was_correct;
if ( typeof(answer_tracking[question_index]) === 'undefined' ) {
answer_tracking[question_index] = was_correct;
cover.find('.question_' + question_index).addClass(
'first_guess_' +
(was_correct ? 'right' : 'wrong')
);
}
self.update_how_you_did_element();
self.display_answer(self.quiz_data[question_index], question_index, self.quiz_data[question_index].possible_answers[answer_index]);
self.quiz_data[question_index].previously_selected = self.quiz_data[question_index].possible_answers[answer_index];
});
}
for (var i = 0; i < question.possible_answers.length; i++) {
var answer_data = question.possible_answers[i];
var possible_answer = $('<li><p class="bg-warning possible_answer answer_' + i + '">' + answer_data.answer + '</p></li>');
bindClick(question_index, i, possible_answer);
answers_container.append(possible_answer);
}
return answers_container;
},
add_display_in_correct_place: function(container, place_in_display_elements, slide) {
for ( var i = place_in_display_elements; i > 0; i-- ) {
if (self.possible_display_elements[i - 1].finder(container).length ) {
self.possible_display_elements[i - 1].finder(container)
.after( self.possible_display_elements[place_in_display_elements].create_element(slide) );
return;
}
}
container.prepend(
self.possible_display_elements[place_in_display_elements].create_element(slide)
);
},
display_answer : function(question, question_index, answer) {
var displayed_slide = question.previously_selected ?
question.previously_selected :
question.question;
var slide = container_elem.find('.question_' + question_index + ' .question');
slide.addClass('revealed_answer');
for (var i = 0; i < self.possible_display_elements.length; i++) {
var display_value = self.possible_display_elements[i].name;
if ( answer[display_value] !== displayed_slide[display_value] ) {
if ( !answer[display_value] ) {
self.possible_display_elements[i].finder(slide).remove();
} else if ( !displayed_slide[display_value] ) {
self.add_display_in_correct_place(slide, i, answer);
} else {
self.possible_display_elements[i].finder(slide).before(
self.possible_display_elements[i].create_element( answer )
).remove();
}
}
}
},
create_cover : function() {
cover = $('#' + self.container);
container_elem = $('<ul></ul>');
cover.append(container_elem);
container_elem.addClass('quiz');
container_elem.css('padding', '0px');
},
update_how_you_did_element: function() {
self.right_answers = 0;
var user_answers = self.cheating ? cheater_answer_tracking : answer_tracking;
var unfinished = false;
for (var i = 0; i < self.quiz_data.length; i++) {
if (typeof(answer_tracking[i]) === 'undefined') {
unfinished = true;
}
if (user_answers[i]) {
self.right_answers++;
}
}
var html;
if (unfinished && typeof(this.not_finished_html) !== 'undefined') {
html = this.not_finished_html;
} else {
html = this.results_data[self.right_answers];
}
how_you_did_element.html(html);
if (self.oneByOne) {
self.setHowYouDidTriggers();
}
}
};
return quiz.init(quiz_data, results_data, options);
};
$.fn.quiz = function(quiz_data, results_data, options) {
if (!options) { options = results_data; results_data = null; }
if (!options) { options = {}; }
options.container = this.attr('id');
this.quiz = $.quiz(quiz_data, results_data, options);
return this;
};
})(Zepto);
</script>
<script type="text/javascript">
var quiz = $('#quiz').quiz('<a_google_spreadsheeet>', { oneByOne: "aName" });
$(document).mouseleave(function () {
google.script.run.mouseOffPage();
});
</script>
</body>
</html>
@ottograjeda
Copy link
Author

ga_other_gist_3a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment