Last active
August 5, 2019 18:45
-
-
Save ottograjeda/b5b182c00f70861ef1e630dda3d64c4d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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: [email protected]. 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> |
Author
ottograjeda
commented
Jan 15, 2019
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment