first import book

This commit is contained in:
Johann Dreo 2015-03-03 15:56:44 +01:00
commit dfd9c869d5
30 changed files with 47797 additions and 0 deletions

View file

@ -0,0 +1,468 @@
var elem; // current audio element playing
var currIndex; // current index
var len; // current length of audio files for tour
var buttonCount; // number of audio tour buttons
var aname; // the audio file name
var ahash; // hash of the audio file name to the lines to highlight
var theDivid; // div id
var afile; // file name for audio
var playing = false; // flag to say if playing or not
var tourName;
String.prototype.replaceAll = function (target, replacement) {
return this.split(target).join(replacement);
};
// function to display the audio tours
var createAudioTourHTML = function (divid, code, bnum, audio_text) {
// Replacing has been done here to make sure special characters in the code are displayed correctly
code = code.replaceAll("*doubleq*", "\"");
code = code.replaceAll("*singleq*", "'");
code = code.replaceAll("*open*", "(");
code = code.replaceAll("*close*", ")");
code = code.replaceAll("*nline*", "<br/>");
var codeArray = code.split("<br/>");
var audio_hash = new Array();
var bval = new Array();
var atype = audio_text.replaceAll("*doubleq*", "\"");
var audio_type = atype.split("*atype*");
for (var i = 0; i < audio_type.length - 1; i++) {
audio_hash[i] = audio_type[i];
var aword = audio_type[i].split(";");
bval.push(aword[0]);
}
var first = "<pre><div id='" + divid + "_l1'>" + "1. " + codeArray[0] + "</div>";
num_lines = codeArray.length;
for (var i = 1; i < num_lines; i++) {
var sec = "<div id='" + divid + "_l" + (i + 1) + "'>" + (i + 1) + ". " + codeArray[i] + "</div>";
var next = first.concat(sec);
first = next;
}
first = first + "</pre>"
//laying out the HTML content
var bcount = 0;
var html_string = "<div class='modal-lightsout'></div><div class='modal-profile'><h3>Take an audio tour!</h3><div class='modal-close-profile'></div><p id='windowcode'></p><p id='" + divid + "_audiocode'></p>";
html_string += "<p id='status'></p>";
html_string += "<input type='image' src='../_static/first.png' width='25' id='first_audio' name='first_audio' title='Play first audio in tour' alt='Play first audio in tour' disabled/>" + "<input type='image' src='../_static/prev.png' width='25' id='prev_audio' name='prev_audio' title='Play previous audio in tour' alt='Play previous audio in tour' disabled/>" + "<input type='image' src='../_static/pause.png' width='25' id='pause_audio' name='pause_audio' title='Pause current audio' alt='Pause current audio' disabled/><input type='image' src='../_static/next.png' width ='25' id='next_audio' name='next_audio' title='Play next audio in tour' alt='Play next audio in tour' disabled/><input type='image' src='../_static/last.png' width ='25' id='last_audio' name='last_audio' title='Play last audio in tour' alt='Play last audio in tour' disabled/><br/>";
for (var i = 0; i < audio_type.length - 1; i++) {
html_string += "<input type='button' style='margin-right:5px;' class='btn btn-default btn-sm' id='button_audio_" + i + "' name='button_audio_" + i + "' value=" + bval[i] + " />";
bcount++;
}
//html_string += "<p id='hightest'></p><p id='hightest1'></p><br/><br/><p id='test'></p><br/><p id='audi'></p></div>";
html_string += "</div>";
$('#cont').html(html_string);
$('#windowcode').html(first);
// Position modal box in the center of the page
$.fn.center = function () {
this.css("position", "absolute");
// y position
this.css("top", ($(window).scrollTop() + $(navbar).height() + 10 + "px"));
// show window on the left so that you can see the output from the code still
this.css("left", ($(window).scrollLeft() + "px"));
return this;
}
$(".modal-profile").center();
$('.modal-profile').fadeIn("slow");
//$('.modal-lightsout').css("height", $(document).height());
$('.modal-lightsout').fadeTo("slow", .5);
$('.modal-close-profile').show();
// closes modal box once close link is clicked, or if the lights out divis clicked
$('.modal-close-profile, .modal-lightsout').click(function () {
if (playing) {
elem.pause();
}
//log change to db
logBookEvent({'event': 'Audio', 'change': 'closeWindow', 'div_id': divid});
$('.modal-profile').fadeOut("slow");
$('.modal-lightsout').fadeOut("slow");
});
// Accommodate buttons for a maximum of five tours
$('#' + 'button_audio_0').click(function () {
tour(divid, audio_hash[0], bcount);
});
$('#' + 'button_audio_1').click(function () {
tour(divid, audio_hash[1], bcount);
});
$('#' + 'button_audio_2').click(function () {
tour(divid, audio_hash[2], bcount);
});
$('#' + 'button_audio_3').click(function () {
tour(divid, audio_hash[3], bcount);
});
$('#' + 'button_audio_4').click(function () {
tour(divid, audio_hash[4], bcount);
});
// handle the click to go to the next audio
$('#first_audio').click(function () {
firstAudio();
});
// handle the click to go to the next audio
$('#prev_audio').click(function () {
prevAudio();
});
// handle the click to pause or play the audio
$('#pause_audio').click(function () {
pauseAndPlayAudio();
});
// handle the click to go to the next audio
$('#next_audio').click(function () {
nextAudio();
});
// handle the click to go to the next audio
$('#last_audio').click(function () {
lastAudio();
});
// make the image buttons look disabled
$("#first_audio").css('opacity', 0.25);
$("#prev_audio").css('opacity', 0.25);
$("#pause_audio").css('opacity', 0.25);
$("#next_audio").css('opacity', 0.25);
$("#last_audio").css('opacity', 0.25);
};
var tour = function (divid, audio_type, bcount) {
// set globals
buttonCount = bcount;
theDivid = divid;
// enable prev, pause/play and next buttons and make visible
$('#first_audio').removeAttr('disabled');
$('#prev_audio').removeAttr('disabled');
$('#pause_audio').removeAttr('disabled');
$('#next_audio').removeAttr('disabled');
$('#last_audio').removeAttr('disabled');
$("#first_audio").css('opacity', 1.0);
$("#prev_audio").css('opacity', 1.0);
$("#pause_audio").css('opacity', 1.0);
$("#next_audio").css('opacity', 1.0);
$("#last_audio").css('opacity', 1.0);
// disable tour buttons
for (var i = 0; i < bcount; i++)
$('#button_audio_' + i).attr('disabled', 'disabled');
var atype = audio_type.split(";");
var name = atype[0].replaceAll("\"", " ");
tourName = name;
$('#status').html("Starting the " + name);
//log tour type to db
logBookEvent({'event': 'Audio', 'tour type': name, 'div_id': divid});
var max = atype.length;
var str = "";
ahash = new Array();
aname = new Array();
for (i = 1; i < max - 1; i++) {
var temp = atype[i].split(":");
var temp_line = temp[0];
var temp_aname = temp[1];
var akey = temp_aname.substring(1, temp_aname.length);
var lnums = temp_line.substring(1, temp_line.length);
//alert("akey:"+akey+"lnum:"+lnums);
// str+="<audio id="+akey+" preload='auto'><source src='http://ice-web.cc.gatech.edu/ce21/audio/"+
// akey+".mp3' type='audio/mpeg'><source src='http://ice-web.cc.gatech.edu/ce21/audio/"+akey+
// ".ogg' type='audio/ogg'>Your browser does not support the audio tag</audio>";
str += "<audio id=" + akey + " preload='auto' ><source src='../_static/audio/" + akey +
".wav' type='audio/wav'><source src='../_static/audio/" +
akey + ".mp3' type='audio/mpeg'><br />Your browser does not support the audio tag</audio>";
ahash[akey] = lnums;
aname.push(akey);
}
var ahtml = "#" + divid + "_audiocode";
$(ahtml).html(str); // set the html to the audio tags
len = aname.length; // set the number of audio file in the tour
// start at the first audio
currIndex = 0;
// play the first audio in the tour
playCurrIndexAudio();
};
function handlePlaying() {
// if playing audio pause it
if (playing) {
elem.pause();
// unbind current ended
$('#' + afile).unbind('ended');
// unhighlight the prev lines
unhighlightLines(theDivid, ahash[aname[currIndex]]);
}
}
var firstAudio = function () {
// if audio is playing handle it
handlePlaying();
//log change to db
logBookEvent({'event': 'Audio', 'change': 'first', 'div_id': theDivid});
// move to the first audio
currIndex = 0;
// start at the first audio
playCurrIndexAudio();
};
var prevAudio = function () {
// if there is a previous audio
if (currIndex > 0)
{
// if audio is playing handle it
handlePlaying();
//log change to db
logBookEvent({'event': 'Audio', 'change': 'prev', 'div_id': theDivid});
// move to previous to the current (but the current index has moved to the next)
currIndex = currIndex - 1;
// start at the prev audio
playCurrIndexAudio();
}
};
var nextAudio = function () {
// if audio is playing handle it
handlePlaying();
//log change to db
logBookEvent({'event': 'Audio', 'change': 'next', 'div_id': theDivid});
// if not at the end
if (currIndex < (len - 1))
{
// start at the next audio
currIndex = currIndex + 1;
playCurrIndexAudio();
}
else if (currIndex == (len - 1))
{
handleTourEnd();
}
};
var lastAudio = function () {
// if audio is playing handle it
handlePlaying();
//log change to db
logBookEvent({'event': 'Audio', 'change': 'last', 'div_id': theDivid});
// move to the last audio
currIndex = len - 1;
// start at last
playCurrIndexAudio();
};
// play the audio at the current index
var playCurrIndexAudio = function () {
// set playing to false
playing = false;
// play the current audio and highlight the lines
playaudio(currIndex, aname, theDivid, ahash);
};
// handle the end of the tour
var handleTourEnd = function() {
$('#status').html(" The " + tourName + " Ended");
// disable the prev, pause/play, and next buttons and make them more invisible
$('#first_audio').attr('disabled', 'disabled');
$('#prev_audio').attr('disabled', 'disabled');
$('#pause_audio').attr('disabled', 'disabled');
$('#next_audio').attr('disabled', 'disabled');
$('#last_audio').attr('disabled', 'disabled');
$("#first_audio").css('opacity', 0.25);
$("#prev_audio").css('opacity', 0.25);
$("#pause_audio").css('opacity', 0.25);
$("#next_audio").css('opacity', 0.25);
$("#last_audio").css('opacity', 0.25);
// enable the tour buttons
for (var j = 0; j < buttonCount; j++)
$('#button_audio_' + j).removeAttr('disabled');
}
// only call this one after the first time
var outerAudio = function () {
// unbind ended
$('#' + afile).unbind('ended');
// set playing to false
playing = false;
// unhighlight previous lines from the last audio
unhighlightLines(theDivid, ahash[aname[currIndex]]);
// increment the currIndex to point to the next one
currIndex++;
// if the end of the tour reset the buttons
if (currIndex == len) {
handleTourEnd();
}
// else not done yet so play the next audio
else {
// play the audio at the current index
playCurrIndexAudio();
}
};
// play the audio now that it is ready
var playWhenReady = function(afile,divid,ahash) {
// unbind current
$('#' + afile).unbind('canplaythrough');
//console.log("in playWhenReady " + elem.duration);
$('#status').html("Playing the " + tourName);
elem.currentTime = 0;
highlightLines(divid, ahash[afile]);
$('#' + afile).bind('ended',function() {
outerAudio();
});
playing = true;
elem.play();
};
// play the audio at the specified index i and set the duration and highlight the lines
var playaudio = function (i, aname, divid, ahash) {
afile = aname[i];
elem = document.getElementById(afile);
// if this isn't ready to play yet - no duration yet then wait
//console.log("in playaudio " + elem.duration);
if (isNaN(elem.duration) || elem.duration == 0)
{
// set the status
$('#status').html("Loading audio. Please wait.");
$('#' + afile).bind('canplaythrough',function() {
playWhenReady(afile,divid,ahash);
});
}
// otherwise it is ready so play it
else
playWhenReady(afile,divid,ahash);
};
// pause if playing and play if paused
var pauseAndPlayAudio = function () {
var btn = document.getElementById('pause_audio');
// if paused and clicked then continue from current
if (elem.paused) {
// calcualte the time left to play in milliseconds
counter = (elem.duration - elem.currentTime) * 1000;
elem.play(); // start the audio from current spot
document.getElementById("pause_audio").src = "../_static/pause.png";
document.getElementById("pause_audio").title = "Pause current audio";
//log change to db
logBookEvent({'event': 'Audio', 'change': 'play', 'div_id': theDivid});
}
// if audio was playing pause it
else if (playing) {
elem.pause(); // pause the audio
document.getElementById("pause_audio").src = "../_static/play.png";
document.getElementById("pause_audio").title = "Play paused audio";
//log change to db
logBookEvent({'event': 'Audio', 'change': 'pause', 'div_id': theDivid});
}
};
// process the lines
var processLines = function (divid, lnum, color) {
var comma = lnum.split(",");
if (comma.length > 1) {
for (i = 0; i < comma.length; i++) {
setBackroundForLines(divid, comma[i], color);
}
}
else {
setBackroundForLines(divid, lnum, color);
}
};
// unhighlight the lines - set the background back to transparent
var unhighlightLines = function (divid, lnum) {
processLines(divid, lnum, 'transparent');
};
// highlight the lines - set the background to a yellow color
var highlightLines = function (divid, lnum) {
processLines(divid, lnum, '#ffff99');
};
// set the background to the passed color
var setBackroundForLines = function (divid, lnum, color) {
var hyphen = lnum.split("-");
// if a range of lines
if (hyphen.length > 1) {
var start = parseInt(hyphen[0]);
var end = parseInt(hyphen[1]) + 1;
for (var k = start; k < end; k++) {
//alert(k);
var str = "#" + divid + "_l" + k;
if ($(str).text() != "")
$(str).css('background-color', color);
//$(str).effect("highlight",{},(dur*1000)+4500);
}
}
else {
//alert(lnum);
var str = "#" + divid + "_l" + lnum;
$(str).css('background-color', color);
//$(str).effect("highlight",{},(dur*1000)+4500);
}
};

View file

@ -0,0 +1,204 @@
Animator = function(m, v, divid)
{
this.model = m
this.viewer = v
this.timer = null
this.cursor = -1
this.sc = document.getElementById(divid+"_canvas")
this.ctx = this.sc.getContext("2d")
this.sc.width = this.sc.width
this.speed = 75
this.script = this.model.init() //does the animation and sends script back
this.viewer.init(this.ctx)
}
Animator.prototype.getContext=function()
{
return this.ctx
}
Animator.prototype.incCursor=function()
{
if (this.cursor < this.script.length-1)
this.cursor = this.cursor + 1
}
Animator.prototype.decCursor=function()
{
if (this.cursor > 0)
this.cursor = this.cursor -1
}
Animator.prototype.getCursor=function()
{
return this.cursor
}
Animator.prototype.setCursor=function(newc)
{
this.cursor = newc
}
Animator.prototype.run = function(animobj)
{
if (this.timer == null)
this.timer = setInterval(animobj+".forward()",this.speed)
}
Animator.prototype.stop = function()
{
clearInterval(this.timer)
this.timer=null
}
Animator.prototype.forward = function()
{
this.incCursor()
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
if (this.getCursor() == this.script.length-1 && this.timer != null)
{
clearInterval(this.timer)
this.timer = null
}
}
Animator.prototype.backward = function()
{
this.decCursor()
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.end = function()
{
this.setCursor(this.script.length-1)
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.begin = function()
{
this.setCursor(0)
this.sc.width=this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.init = function()
{
this.setCursor(0)
this.sc.width = this.sc.width
this.viewer.render(this.script[0])
}
init1 = function()
{
a = new Animator(new BubbleSortModel(), new BarViewer())
a.init()
}
init2 = function()
{
a = new Animator(new BubbleSortModel(), new ScatterViewer())
a.init()
}
init3 = function()
{
a = new Animator(new BubbleSortModel(), new BoxViewer())
a.init()
}
init4 = function()
{
a = new Animator(new SelectionSortModel(), new BarViewer())
a.init()
}
init5 = function()
{
a = new Animator(new SelectionSortModel(), new ScatterViewer())
a.init()
}
init6 = function()
{
a = new Animator(new SelectionSortModel(), new BoxViewer())
a.init()
}
init7 = function()
{
a = new Animator(new InsertionSortModel(), new BarViewer())
a.init()
}
init8 = function()
{
a = new Animator(new InsertionSortModel(), new ScatterViewer())
a.init()
}
init9 = function()
{
a = new Animator(new InsertionSortModel(), new BoxViewer())
a.init()
}
init10 = function()
{
a = new Animator(new ShellSortModel(), new BarViewer())
a.init()
}
init11 = function()
{
a = new Animator(new ShellSortModel(), new ScatterViewer())
a.init()
}
init12 = function()
{
a = new Animator(new ShellSortModel(), new BoxViewer())
a.init()
}
init13 = function()
{
a = new Animator(new MergeSortModel(), new BarViewer())
a.init()
}
init14 = function()
{
a = new Animator(new MergeSortModel(), new ScatterViewer())
a.init()
}
init15 = function()
{
a = new Animator(new MergeSortModel(), new BoxViewer())
a.init()
}
init16 = function()
{
a = new Animator(new QuickSortModel(), new BarViewer())
a.init()
}
init17 = function()
{
a = new Animator(new QuickSortModel(), new ScatterViewer())
a.init()
}
init18 = function()
{
a = new Animator(new QuickSortModel(), new BoxViewer())
a.init()
}

366
book/common/js/assess.js Normal file
View file

@ -0,0 +1,366 @@
var add_button = function (divid, expected, feedArray) {
var bdiv = divid + "_bt";
var element = document.createElement("input");
element.setAttribute("type", "button");
element.setAttribute("value", "Check Me!");
element.setAttribute("name", "do check");
element.onclick = function () {
checkMCMFRandom(divid, expected, feedArray);
};
$("#" + bdiv).html(element);
};
var feedbackMCMFRandom = function (divid, correct, feedbackText) {
if (correct) {
$(divid).html('Correct!! ' + feedbackText);
//$(divid).css('background-color', '#C8F4AD');
$(divid).attr('class','alert alert-success');
} else {
if (feedbackText == null) {
feedbackText = '';
}
$(divid).html("Incorrect. " + feedbackText);
//$(divid).css('background-color', '#F4F4AD');
$(divid).attr('class','alert alert-danger')
}
};
var checkFIBStorage = function (divid, blankid, expected, feedback, casi) {
var given = document.getElementById(blankid).value;
// update number of trials??
// log this to the db
modifiers = '';
if (casi) {
modifiers = 'i'
}
var patt = RegExp(expected, modifiers);
var isCorrect = patt.test(given);
if (!isCorrect) {
fbl = feedback;
for (var i = 0; i < fbl.length; i++) {
patt = RegExp(fbl[i][0]);
if (patt.test(given)) {
feedback = fbl[i][1];
break;
}
}
}
// store the answer in local storage
var storage_arr = new Array();
storage_arr.push(given);
storage_arr.push(expected);
localStorage.setItem(divid, storage_arr.join(";"));
feedBack('#' + divid + '_feedback', isCorrect, feedback);
var answerInfo = 'answer:' + given + ":" + (isCorrect ? 'correct' : 'no');
logBookEvent({'event': 'fillb', 'act': answerInfo, 'div_id': divid});
document.getElementById(divid + '_bcomp').disabled = false;
};
var feedBack = function (divid, correct, feedbackText) {
if (correct) {
$(divid).html('You are Correct!');
//$(divid).css('background-color', '#C8F4AD');
$(divid).attr('class','alert alert-success');
} else {
if (feedbackText == null) {
feedbackText = '';
}
$(divid).html("Incorrect. " + feedbackText);
//$(divid).css('background-color', '#F4F4AD');
$(divid).attr('class','alert alert-danger');
}
};
/*
Multiple Choice with Feedback for each answer
*/
var feedBackMCMF = function (divid, correct, feedbackText) {
if (correct) {
$(divid).html('Correct!! ' + feedbackText);
//$(divid).css('background-color', '#C8F4AD');
$(divid).attr('class','alert alert-success');
} else {
if (feedbackText == null) {
feedbackText = '';
}
$(divid).html("Incorrect. " + feedbackText);
//$(divid).css('background-color', '#F4F4AD');
$(divid).attr('class','alert alert-danger');
}
};
/*
Multiple Choice with Multiple Answers
*/
var feedBackMCMA = function (divid, numCorrect, numNeeded, numGiven, feedbackText) {
var answerStr = "answers";
if (numGiven == 1) answerStr = "answer";
if (numCorrect == numNeeded && numNeeded == numGiven) {
$(divid).html('Correct! <br />' + feedbackText);
//$(divid).css('background-color', '#C8F4AD');
$(divid).attr('class', 'alert alert-success');
} else {
$(divid).html("Incorrect. " + "You gave " + numGiven +
" " + answerStr + " and got " + numCorrect + " correct of " +
numNeeded + " needed.<br /> " + feedbackText);
//$(divid).css('background-color', '#F4F4AD');
$(divid).attr('class', 'alert alert-danger');
}
};
/* Randomize options */
var createHTML_MCMFRandom = function (divid, answerString, feedString, corrAnswer) {
var i, j, k, l, len;
var arr = new Array();
var fr = new Array();
var alpha = ['a', 'b', 'c', 'd', 'e'];
var ansArray = new Array();
var feedArray = new Array();
var hash = new Array();
arr = answerString.split("*separator*");
fr = feedString.split("*separator*");
for (j = 0; j < arr.length - 1; j++) {
ansArray[j] = arr[j];
hash[ansArray[j]] = fr[j];
}
i = ansArray.length;
len = i;
if (i == 0) return false;
while (--i) {
var j = Math.floor(Math.random() * ( i + 1 ));
var tempi = ansArray[i];
var tempj = ansArray[j];
ansArray[i] = tempj;
ansArray[j] = tempi;
}
for (i = 0; i < ansArray.length; i++) {
k = ansArray[i];
feedArray[i] = hash[k];
}
for (l = 0; l < len; l++) {
var rad = "<input type='radio' name='group1' value='" + l + "'/>" + "<label for= 'opt_" + l + "'>" + " " + alpha[l] + ") " + ansArray[l] + "</label><br />";
var opdiv = divid + "_op" + (l + 1);
$("#" + opdiv).html(rad);
}
var index = ansArray.indexOf(corrAnswer);
add_button(divid, index, feedArray);
return true;
};
var checkMCMFRandom = function (divid, expected, feed) {
var given;
var feedback = null;
var buttonObjs = document.forms[divid + "_form"].elements.group1;
for (var i = buttonObjs.length - 1; i >= 0; i--) {
if (buttonObjs[i].checked) {
given = buttonObjs[i].value;
feedback = feed[i];
}
}
// log the answer
var answerInfo = 'answer:' + given + ":" + (given == expected ? 'correct' : 'no');
logBookEvent({'event': 'mChoice', 'act': answerInfo, 'div_id': divid});
// give the user feedback
feedbackMCMFRandom('#' + divid + '_feedback', given == expected, feedback);
};
/* Local Storage */
var resetPage = function (divid) {
var i;
var id = new Array();
var keyArr = new Array();
id = divid.split("_");
var pageNum = id[1];
location.reload();
//erasing data from local storage
var len = localStorage.length;
if (len > 0) {
for (i = 0; i < len; i++) {
var key = localStorage.key(i);
var str = key.substring(0, 1);
if (str === pageNum) {
keyArr.push(key);
}
}
}
for (i = 0; i < keyArr.length; i++)
localStorage.removeItem(keyArr[i]);
};
var checkRadio = function (divid) {
// This function repopulates MCMF questions with a user's previous answers,
// which were previously stored into local storage
var qnum = divid;
var len = localStorage.length;
//retrieving data from local storage
if (len > 0) {
for (var i = 0; i < len; i++) {
var key = localStorage.key(i);
if (key === qnum) {
var ex = localStorage.getItem(key);
var arr = ex.split(";");
var str = key + "_opt_" + arr[0];
$("#" + str).attr("checked", "true");
document.getElementById(divid + '_bcomp').disabled = false;
}
}
}
};
var checkMultipleSelect = function (divid) {
// This function repopulates MCMA questions with a user's previous answers,
// which were stored into local storage.
var qnum = divid;
var len = localStorage.length;
if (len > 0) {
for (var i = 0; i < len; i++) {
var key = localStorage.key(i);
if (key === qnum) {
var ex = localStorage.getItem(key);
var arr = ex.split(";");
var answers = arr[0].split(",");
for (var a = 0; a < answers.length; a++) {
var str = key + "_opt_" + answers[a];
$("#" + str).attr("checked", "true");
document.getElementById(divid + '_bcomp').disabled = false;
}
}
}
}
};
var checkPreviousFIB = function (divid) {
// This function repoplulates FIB questions with a user's previous answers,
// which were stored into local storage
var qnum = divid;
var len = localStorage.length;
if (len > 0) {
for (var i = 0; i < len; i++) {
var key = localStorage.key(i);
if (key === qnum) {
var ex = localStorage.getItem(key);
var arr = ex.split(";");
var str = key + "_ans1";
$("#" + str).attr("value", arr[0]);
document.getElementById(divid + '_bcomp').disabled = false;
}
}
}
};
var checkMCMAStorage = function (divid, expected, feedbackArray) {
var given;
var feedback = "";
var correctArray = expected.split(",");
correctArray.sort();
var givenArray = [];
var correctCount = 0;
var correctIndex = 0;
var givenIndex = 0;
var givenlog = '';
var buttonObjs = document.forms[divid + "_form"].elements.group1;
// loop through the checkboxes
for (var i = 0; i < buttonObjs.length; i++) {
if (buttonObjs[i].checked) { // if checked box
given = buttonObjs[i].value; // get value of this button
givenArray.push(given) // add it to the givenArray
feedback += given + ": " + feedbackArray[i] + "<br />"; // add the feedback
givenlog += given + ",";
}
}
// sort the given array
givenArray.sort();
while (correctIndex < correctArray.length &&
givenIndex < givenArray.length) {
if (givenArray[givenIndex] < correctArray[correctIndex]) {
givenIndex++;
}
else if (givenArray[givenIndex] == correctArray[correctIndex]) {
correctCount++;
givenIndex++;
correctIndex++;
}
else {
correctIndex++;
}
} // end while
// save the data into local storage
var storage_arr = new Array();
storage_arr.push(givenArray);
storage_arr.push(expected);
localStorage.setItem(divid, storage_arr.join(";"));
// log the answer
var answerInfo = 'answer:' + givenlog.substring(0, givenlog.length - 1) + ':' +
(correctCount == correctArray.length ? 'correct' : 'no');
logBookEvent({'event': 'mChoice', 'act': answerInfo, 'div_id': divid});
// give the user feedback
feedBackMCMA('#' + divid + '_feedback', correctCount,
correctArray.length, givenArray.length, feedback);
document.getElementById(divid + '_bcomp').disabled = false;
};
var checkMCMFStorage = function (divid, expected, feedbackArray) {
var given;
var feedback = null;
var buttonObjs = document.forms[divid + "_form"].elements.group1;
for (var i = buttonObjs.length - 1; i >= 0; i--) {
if (buttonObjs[i].checked) {
given = buttonObjs[i].value;
feedback = feedbackArray[i];
}
}
//Saving data in local storage
var storage_arr = new Array();
storage_arr.push(given);
storage_arr.push(expected);
localStorage.setItem(divid, storage_arr.join(";"));
// log the answer
var answerInfo = 'answer:' + given + ":" + (given == expected ? 'correct' : 'no');
logBookEvent({'event': 'mChoice', 'act': answerInfo, 'div_id': divid});
// give the user feedback
feedBackMCMF('#' + divid + '_feedback', given == expected, feedback);
document.getElementById(divid + '_bcomp').disabled = false;
};
// for each form in the div
// get the id of the form
// call checkMe on the form... -- need metadata what kind of question what parms etc
// hidden fields for meta data??? each form defines a checkme function with no parameters
// that calls the actual function that checks the answer properly??
// summarize

950
book/common/js/bookfuncs.js Normal file
View file

@ -0,0 +1,950 @@
/**
* Created by IntelliJ IDEA.
* User: bmiller
* Date: 4/20/11
* Time: 2:01 PM
* To change this template use File | Settings | File Templates.
*/
/*
Copyright (C) 2011 Brad Miller bonelake@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
function handleEdKeys(ed, e) {
if (e.keyCode === 13) {
if (e.ctrlKey) {
e.stop();
runit(ed.parentDiv);
}
else if (e.shiftKey) {
e.stop();
eval(Sk.importMainWithBody("<stdin>", false, ed.selection()));
}
} else {
if (ed.acEditEvent == false || ed.acEditEvent === undefined) {
$('#' + ed.parentDiv + ' .CodeMirror').css('border-top', '2px solid #b43232');
$('#' + ed.parentDiv + ' .CodeMirror').css('border-bottom', '2px solid #b43232');
}
ed.acEditEvent = true;
}
}
cm_editors = {};
function pyStr(x) {
if (x instanceof Array) {
return '[' + x.join(", ") + ']';
} else {
return x
}
}
function outf(text) {
var mypre = document.getElementById(Sk.pre);
// bnm python 3
x = text;
if (x.charAt(0) == '(') {
x = x.slice(1, -1);
x = '[' + x + ']';
try {
var xl = eval(x);
xl = xl.map(pyStr);
x = xl.join(' ');
} catch (err) {
}
}
text = x;
text = text.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br/>");
mypre.innerHTML = mypre.innerHTML + text;
}
function createEditors() {
var edList = new Array();
edList = document.getElementsByClassName("active_code");
for (var i = 0; i < edList.length; i++) {
newEdId = edList[i].id;
var includes = edList[i].getAttribute('prefixcode');
var lang = edList[i].getAttribute('lang');
var first_line = 1;
if (includes !== "undefined") {
includes = eval(includes)
for (var j in includes) {
var edinclude = document.getElementById(includes[j]+'_code')
first_line = first_line + edinclude.textContent.match(/\n/g).length + 1;
}
} else {
first_line = 1;
}
cm_editors[newEdId] = CodeMirror.fromTextArea(edList[i], {
mode: {name: lang,
version: 2,
singleLineStringErrors: false},
lineNumbers: true,
firstLineNumber: first_line,
indentUnit: 4,
indentWithTabs: false,
matchBrackets: true,
extraKeys: {"Tab": "indentMore", "Shift-Tab": "indentLess"},
onKeyEvent: handleEdKeys
}
);
cm_editors[newEdId].parentDiv = edList[i].parentNode.parentNode.id;
//requestCode(edList[i].parentNode.id) // populate with user's code
}
}
function builtinRead(x) {
if (Sk.builtinFiles === undefined || Sk.builtinFiles["files"][x] === undefined)
throw "File not found: '" + x + "'";
return Sk.builtinFiles["files"][x];
}
function createActiveCode(divid,suppliedSource,sid) {
var eNode;
var acblockid;
if (sid !== undefined) {
acblockid = divid + "_" + sid;
} else {
acblockid = divid;
}
edNode = document.getElementById(acblockid);
if (edNode.children.length == 0 ) {
//edNode.style.display = 'none';
edNode.style.backgroundColor = "white";
var editor;
editor = CodeMirror(edNode, {
mode: {name: "python",
version: 2,
singleLineStringErrors: false},
lineNumbers: true,
indentUnit: 4,
tabMode: "indent",
matchBrackets: true,
onKeyEvent:handleEdKeys
});
var myRun = function() {
runit(acblockid);
};
var mySave = function() {
saveEditor(divid);
};
var myLoad = function() {
requestCode(divid,sid);
};
cm_editors[acblockid+"_code"] = editor;
editor.parentDiv = acblockid;
var runButton = document.createElement("button");
runButton.appendChild(document.createTextNode('Run'));
runButton.className = runButton.className + ' btn btn-success';
runButton.onclick = myRun;
edNode.appendChild(runButton);
edNode.appendChild(document.createElement('br'));
if (sid === undefined) { // We don't need load and save buttons for grading
if(isLoggedIn() == true) {
var saveButton = document.createElement("input");
saveButton.setAttribute('type','button');
saveButton.setAttribute('value','Save');
saveButton.className = saveButton.className + ' btn btn-default';
saveButton.onclick = mySave;
edNode.appendChild(saveButton);
var loadButton = document.createElement("input");
loadButton.setAttribute('type','button');
loadButton.setAttribute('value','Load');
loadButton.className = loadButton.className + ' btn btn-default';
loadButton.onclick = myLoad;
edNode.appendChild(loadButton);
} else {
var saveButton = document.createElement("input");
saveButton.setAttribute('type','button');
saveButton.setAttribute('value','Save');
saveButton.className = saveButton.className + ' btn btn-default disabled';
saveButton.setAttribute('data-toggle','tooltip');
saveButton.setAttribute('title','Register or log in to save your code');
edNode.appendChild(saveButton);
$jqTheme(saveButton).tooltip( {
'selector': '',
'placement': 'bottom'
});
var loadButton = document.createElement("input");
loadButton.setAttribute('type','button');
loadButton.setAttribute('value','Load');
loadButton.className = loadButton.className + ' btn btn-default disabled';
loadButton.setAttribute('data-toggle','tooltip');
loadButton.setAttribute('title','Register or log in to load your saved code');
edNode.appendChild(loadButton);
$jqTheme(loadButton).tooltip( {
'selector': '',
'placement': 'bottom'
});
}
}
edNode.appendChild(document.createElement('br'));
var newCanvas = edNode.appendChild(document.createElement("canvas"));
newCanvas.id = acblockid+"_canvas";
newCanvas.height = 400;
newCanvas.width = 400;
newCanvas.style.border = '2px solid black';
newCanvas.style.display = 'none';
var newPre = edNode.appendChild(document.createElement("pre"));
newPre.id = acblockid + "_pre";
newPre.className = "active_out";
myLoad();
if (! suppliedSource ) {
suppliedSource = '\n\n\n\n\n';
}
if (! editor.getValue()) {
suppliedSource = suppliedSource.replace(new RegExp('%22','g'),'"');
suppliedSource = suppliedSource.replace(new RegExp('%27','g'),"'");
editor.setValue(suppliedSource);
}
}
}
function runit(myDiv, theButton, includes, suffix) {
//var prog = document.getElementById(myDiv + "_code").value;
Sk.divid = myDiv;
$(theButton).attr('disabled', 'disabled');
Sk.isTurtleProgram = false;
if (theButton !== undefined) {
Sk.runButton = theButton;
}
$("#" + myDiv + "_errinfo").remove();
var editor = cm_editors[myDiv + "_code"];
if (editor.acEditEvent) {
logBookEvent({'event': 'activecode', 'act': 'edit', 'div_id': myDiv}); // Log the edit event
editor.acEditEvent = false;
}
var prog = "";
var text = "";
if (includes !== undefined) {
// iterate over the includes, in-order prepending to prog
for (var x in includes) {
text = cm_editors[includes[x] + "_code"].getValue();
prog = prog + text + "\n"
}
}
prog = prog + editor.getValue();
var spre = document.getElementById(myDiv + '_suffix');
var suffix = '';
if (spre) {
suffix = spre.innerText || ''; // for some reason Firefox returns undefined when no innerText
}
prog = prog + '\n' + suffix;
var mypre = document.getElementById(myDiv + "_pre");
if (mypre) mypre.innerHTML = '';
Sk.canvas = myDiv + "_canvas";
Sk.pre = myDiv + "_pre";
var can = document.getElementById(Sk.canvas);
// The following lines reset the canvas so that each time the run button
// is pressed the turtle(s) get a clean canvas.
if (can) {
can.width = can.width;
if (Sk.tg) {
Sk.tg.canvasInit = false;
Sk.tg.turtleList = [];
}
}
// set execLimit in milliseconds -- for student projects set this to
// 25 seconds -- just less than Chrome's own timer.
Sk.execLimit = 25000;
// configure Skulpt output function, and module reader
Sk.configure({output: outf,
read: builtinRead,
python3: true
});
var lang = document.getElementById(myDiv).lang;
try {
if(lang === 'python') {
Sk.importMainWithBody("<stdin>", false, prog);
} else if (lang === 'javascript') {
eval(prog);
} else {
// html
$('#'+myDiv+'_iframe').remove()
$('#'+myDiv).append('<iframe class="activehtml" id="' + myDiv + '_iframe" srcdoc="'
+ prog + '">' + '</iframe>');
}
logRunEvent({'div_id': myDiv, 'code': prog, 'errinfo': 'success'}); // Log the run event
} catch (e) {
logRunEvent({'div_id': myDiv, 'code': prog, 'errinfo': e.toString()}); // Log the run event
//alert(e);
addErrorMessage(e, myDiv)
}
if (!Sk.isTurtleProgram) {
$(theButton).removeAttr('disabled');
}
if (typeof(allVisualizers) != "undefined") {
$.each(allVisualizers, function (i, e) {
e.redrawConnectors();
});
}
}
function addErrorMessage(err, myDiv) {
var errHead = $('<h3>').html('Error')
var divEl = document.getElementById(myDiv)
var eContainer = divEl.appendChild(document.createElement('div'))
eContainer.className = 'error alert alert-danger';
eContainer.id = myDiv + '_errinfo';
eContainer.appendChild(errHead[0]);
var errText = eContainer.appendChild(document.createElement('pre'))
var errString = err.toString()
var to = errString.indexOf(":")
var errName = errString.substring(0, to);
errText.innerHTML = errString;
$(eContainer).append('<h3>Description</h3>');
var errDesc = eContainer.appendChild(document.createElement('p'));
errDesc.innerHTML = errorText[errName];
$(eContainer).append('<h3>To Fix</h3>');
var errFix = eContainer.appendChild(document.createElement('p'));
errFix.innerHTML = errorText[errName + 'Fix'];
var moreInfo = '../ErrorHelp/' + errName.toLowerCase() + '.html';
}
var errorText = {};
errorText.ParseError = "A parse error means that Python does not understand the syntax on the line the error message points out. Common examples are forgetting commas beteween arguments or forgetting a : on a for statement";
errorText.ParseErrorFix = "To fix a parse error you just need to look carefully at the line with the error and possibly the line before it. Make sure it conforms to all of Python's rules.";
errorText.TypeError = "Type errors most often occur when an expression tries to combine two objects with types that should not be combined. Like raising a string to a power";
errorText.TypeErrorFix = "To fix a type error you will most likely need to trace through your code and make sure the variables have the types you expect them to have. It may be helpful to print out each variable along the way to be sure its value is what you think it should be.";
errorText.NameError = "A name error almost always means that you have used a variable before it has a value. Often this may be a simple typo, so check the spelling carefully.";
errorText.NameErrorFix = "Check the right hand side of assignment statements and your function calls, this is the most likely place for a NameError to be found.";
errorText.ValueError = "A ValueError most often occurs when you pass a parameter to a function and the function is expecting one type and you pass another.";
errorText.ValueErrorFix = "The error message gives you a pretty good hint about the name of the function as well as the value that is incorrect. Look at the error message closely and then trace back to the variable containing the problematic value.";
errorText.AttributeError = "This error message is telling you that the object on the left hand side of the dot, does not have the attribute or method on the right hand side.";
errorText.AttributeErrorFix = "The most common variant of this message is that the object undefined does not have attribute X. This tells you that the object on the left hand side of the dot is not what you think. Trace the variable back and print it out in various places until you discover where it becomes undefined. Otherwise check the attribute on the right hand side of the dot for a typo.";
errorText.TokenError = "Most of the time this error indicates that you have forgotten a right parenthesis or have forgotten to close a pair of quotes.";
errorText.TokenErrorFix = "Check each line of your program and make sure that your parenthesis are balanced.";
errorText.TimeLimitError = "Your program is running too long. Most programs in this book should run in less than 10 seconds easily. This probably indicates your program is in an infinite loop.";
errorText.TimeLimitErrorFix = "Add some print statements to figure out if your program is in an infinte loop. If it is not you can increase the run time with sys.setExecutionLimit(msecs)";
errorText.Error = "Your program is running for too long. Most programs in this book should run in less than 30 seconds easily. This probably indicates your program is in an infinite loop.";
errorText.ErrorFix = "Add some print statements to figure out if your program is in an infinte loop. If it is not you can increase the run time with sys.setExecutionLimit(msecs)";
errorText.SyntaxError = "This message indicates that Python can't figure out the syntax of a particular statement. Some examples are assigning to a literal, or a function call";
errorText.SyntaxErrorFix = "Check your assignment statments and make sure that the left hand side of the assignment is a variable, not a literal or a function.";
errorText.IndexError = "This message means that you are trying to index past the end of a string or a list. For example if your list has 3 things in it and you try to access the item at position 3 or more.";
errorText.IndexErrorFix = "Remember that the first item in a list or string is at index position 0, quite often this message comes about because you are off by one. Remember in a list of length 3 the last legal index is 2";
errorText.URIError = "";
errorText.URIErrorFix = "";
errorText.ImportError = "This error message indicates that you are trying to import a module that does not exist";
errorText.ImportErrorFix = "One problem may simply be that you have a typo. It may also be that you are trying to import a module that exists in 'real' Python, but does not exist in this book. If this is the case, please submit a feature request to have the module added.";
errorText.ReferenceError = "This is most likely an internal error, particularly if the message references the console.";
errorText.ReferenceErrorFix = "Try refreshing the webpage, and if the error continues, submit a bug report along with your code";
errorText.ZeroDivisionError = "This tells you that you are trying to divide by 0. Typically this is because the value of the variable in the denominator of a division expression has the value 0";
errorText.ZeroDivisionErrorFix = "You may need to protect against dividing by 0 with an if statment, or you may need to rexamine your assumptions about the legal values of variables, it could be an earlier statment that is unexpectedly assigning a value of zero to the variable in question.";
errorText.RangeError = "This message almost always shows up in the form of Maximum call stack size exceeded.";
errorText.RangeErrorFix = "This always occurs when a function calls itself. Its pretty likely that you are not doing this on purpose. Except in the chapter on recursion. If you are in that chapter then its likely you haven't identified a good base case.";
errorText.InternalError = "An Internal error may mean that you've triggered a bug in our Python";
errorText.InternalErrorFix = "Report this error, along with your code as a bug.";
errorText.IndentationError = "This error occurs when you have not indented your code properly. This is most likely to happen as part of an if, for, while or def statement.";
errorText.IndentationErrorFix = "Check your if, def, for, and while statements to be sure the lines are properly indented beneath them. Another source of this error comes from copying and pasting code where you have accidentally left some bits of code lying around that don't belong there anymore.";
errorText.NotImplementedError = "This error occurs when you try to use a builtin function of Python that has not been implemented in this in-browser version of Python.";
errorText.NotImplementedErrorFix = "For now the only way to fix this is to not use the function. There may be workarounds. If you really need this builtin function then file a bug report and tell us how you are trying to use the function.";
function logBookEvent(eventInfo) {
eventInfo.course = eBookConfig.course;
if (eBookConfig.logLevel > 0) {
jQuery.get(eBookConfig.ajaxURL + 'hsblog', eventInfo); // Log the run event
}
}
function logRunEvent(eventInfo) {
eventInfo.course = eBookConfig.course;
if (eBookConfig.logLevel > 0) {
jQuery.post(eBookConfig.ajaxURL + 'runlog', eventInfo); // Log the run event
}
}
function saveSuccess(data, status, whatever) {
if (data.redirect) {
alert("Did not save! It appears you are not logged in properly")
} else if (data == "") {
alert("Error: Program not saved");
}
else {
var acid = eval(data)[0];
if (acid.indexOf("ERROR:") == 0) {
alert(acid);
} else {
// use a tooltip to provide some success feedback
var save_btn = $("#" + acid + "_saveb");
save_btn.attr('title', 'Saved your code.');
opts = {
'trigger': 'manual',
'placement': 'bottom',
'delay': { show: 100, hide: 500}
};
save_btn.tooltip(opts);
save_btn.tooltip('show');
setTimeout(function () {
save_btn.tooltip('destroy')
}, 4000);
$('#' + acid + ' .CodeMirror').css('border-top', '2px solid #aaa');
$('#' + acid + ' .CodeMirror').css('border-bottom', '2px solid #aaa');
}
}
}
function saveEditor(divName) {
// get editor from div name
var editor = cm_editors[divName + "_code"];
var data = {acid: divName, code: editor.getValue()};
$(document).ajaxError(function (e, jqhxr, settings, exception) {
alert("Request Failed for" + settings.url)
});
jQuery.post(eBookConfig.ajaxURL + 'saveprog', data, saveSuccess);
if (editor.acEditEvent) {
logBookEvent({'event': 'activecode', 'act': 'edit', 'div_id': divName}); // Log the run event
editor.acEditEvent = false;
}
logBookEvent({'event': 'activecode', 'act': 'save', 'div_id': divName}); // Log the run event
}
function requestCode(divName, sid) {
var editor = cm_editors[divName + "_code"];
var data = {acid: divName};
if (sid !== undefined) {
data['sid'] = sid;
}
logBookEvent({'event': 'activecode', 'act': 'load', 'div_id': divName}); // Log the run event
jQuery.get(eBookConfig.ajaxURL + 'getprog', data, loadEditor);
}
function loadEditor(data, status, whatever) {
// function called when contents of database are returned successfully
var res = eval(data)[0];
var editor;
if (res.sid) {
editor = cm_editors[res.acid + "_" + res.sid + "_code"];
} else {
editor = cm_editors[res.acid + "_code"];
}
var loadbtn = $("#"+res.acid+"_loadb");
if (res.source) {
editor.setValue(res.source);
loadbtn.tooltip({'placement': 'bottom',
'title': "Loaded your saved code.",
'trigger': 'manual'
});
} else {
loadbtn.tooltip({'placement': 'bottom',
'title': "No saved code.",
'trigger': 'manual'
});
}
loadbtn.tooltip('show');
setTimeout(function () {
loadbtn.tooltip('destroy')
}, 4000);
}
function disableAcOpt() {
$jqTheme('button.ac_opt').each(function (index, value) {
value.className = value.className + ' disabled';
$jqTheme(value).attr('onclick', 'return false;');
$jqTheme(value).attr('data-toggle', 'tooltip');
if ($jqTheme(value).text() == 'Save') {
$jqTheme(value).attr('title', 'Register or log in to save your code');
} else if ($jqTheme(value).text() == 'Load') {
$jqTheme(value).attr('title', 'Register or log in to load your saved code');
}
$jqTheme(value).tooltip({
'selector': '',
'delay': { show: 100, hide: 50 },
'placement': 'bottom',
'animation': true
});
});
}
function comment(blockid) {
$.modal('<iframe width="600" height="400" src="/getcomment?id=' + blockid + '" style="background-color: white">', {
//$.modal('<form><textarea name="content"></textarea><input type="submit" name="submit" > </form>', {
overlayClose: true,
closeHTML: "",
containerCss: {
width: 600,
height: 400,
backgroundColor: "#fff"
}
});
}
function sendGrade(grade, sid, acid, id) {
data = {'sid': sid, 'acid': acid, 'grade': grade, 'id': id};
jQuery.get(eBookConfig.ajaxURL + 'savegrade', data);
}
function sendComment(comment, sid, acid, id) {
data = {'sid': sid, 'acid': acid, 'comment': comment, 'id': id};
jQuery.get(eBookConfig.ajaxURL + 'savegrade', data);
}
function gotUser(data, status, whatever) {
var mess = '';
var caughtErr = false;
var d;
try {
d = eval(data)[0];
} catch (err) {
if (eBookConfig.loginRequired) {
if (confirm("Error: " + err.toString() + "Please report this error! Click OK to continue without logging in. Cancel to retry.")) {
caughtErr = true;
mess = "Not logged in";
disableAcOpt();
$('li.loginout').html('<a href="' + eBookConfig.app + '/default/user/login">Login</a>')
} else {
window.location.href = eBookConfig.app + '/default/user/login?_next=' + window.location.href
}
}
}
if (d.redirect) {
if (eBookConfig.loginRequired) {
window.location.href = eBookConfig.app + '/default/user/login?_next=' + window.location.href
} else {
mess = "Not logged in";
disableAcOpt();
$('li.loginout').html('<a href="' + eBookConfig.app + '/default/user/login">Login</a>')
}
} else {
if (!caughtErr) {
mess = d.email;
eBookConfig.isLoggedIn = true;
addNavbarLoginLink(); // will change navbar login link to say 'Log Out'
enableUserHighlights();
timedRefresh();
}
}
x = $(".loggedinuser").html();
$(".loggedinuser").html(mess);
logBookEvent({
'event': 'page',
'act': 'view',
'div_id': window.location.pathname
})
}
function timedRefresh() {
timeoutPeriod = 4500000; // 75 minutes
$(document).bind("idle.idleTimer", function () {
// After timeout period send the user back to the index. This will force a login
// if needed when they want to go to a particular page. This may not be perfect
// but its an easy way to make sure laptop users are properly logged in when they
// take quizzes and save stuff.
if (location.href.indexOf('index.html') < 0)
location.href = eBookConfig.app + '/static/' + eBookConfig.course + '/index.html'
});
$.idleTimer(timeoutPeriod);
}
function shouldLogin() {
var sli = true;
if (window.location.href.indexOf('file://') > -1)
sli = false;
return sli;
}
function isLoggedIn() {
if (typeof eBookConfig.isLoggedIn !== undefined) {
return eBookConfig.isLoggedIn;
}
return false;
}
function addUserToFooter() {
// test to see if online before doing this.
if (shouldLogin()) {
jQuery.get(eBookConfig.ajaxURL + 'getuser', null, gotUser)
} else {
x = $(".footer").html();
$(".footer").html(x + 'not logged in');
disableAcOpt();
logBookEvent({'event': 'page', 'act': 'view', 'div_id': window.location.pathname})
}
}
function addNavbarLoginLink() {
if (isLoggedIn()) {
$('#profilelink').show();
$('#passwordlink').show();
$('#registerlink').hide();
$('li.loginout').html('<a href="' + eBookConfig.app + '/default/user/logout">Log Out</a>')
} else {
$('#registerlink').show();
$('#profilelink').hide();
$('#passwordlink').hide();
$('li.loginout').html('<a href="' + eBookConfig.app + '/default/user/login">Login</a>')
}
}
/*
Since I don't want to modify the codelens code I'll attach the logging functionality this way.
This actually seems like a better way to do it maybe across the board to separate logging
from the real funcionality. It would also allow a better way of turning off/on logging..
As long as Philip doesn't go and change the id values for the buttons and slider this will
continue to work.... In the best of all worlds we might add a function to the visualizer to
return the buttons, but I'm having a hard time thinking of any other use for that besides mine.
*/
function attachLoggers(codelens, divid) {
codelens.domRoot.find("#jmpFirstInstr").click(function () {
logBookEvent({'event': 'codelens', 'act': 'first', 'div_id': divid});
});
codelens.domRoot.find("#jmpLastInstr").click(function () {
logBookEvent({'event': 'codelens', 'act': 'last', 'div_id': divid});
});
codelens.domRoot.find("#jmpStepBack").click(function () {
logBookEvent({'event': 'codelens', 'act': 'back', 'div_id': divid});
});
codelens.domRoot.find("#jmpStepFwd").click(function () {
logBookEvent({'event': 'codelens', 'act': 'fwd', 'div_id': divid});
});
codelens.domRoot.find("#executionSlider").bind('slide', function (evt, ui) {
logBookEvent({'event': 'codelens', 'act': 'slide', 'div_id': divid});
});
}
function redrawAllVisualizerArrows() {
if (allVisualizers !== undefined) {
for (var v in allVisualizers)
allVisualizers[v].redrawConnectors();
}
}
function getNumUsers() {
$.getJSON(eBookConfig.ajaxURL + 'getnumusers', setNumUsers)
}
function getOnlineUsers() {
$.getJSON(eBookConfig.ajaxURL + 'getnumonline', setOnlineUsers)
}
function setOnlineUsers(data) {
var d = data[0];
$("#numuserspan").text(d.online);
}
function setNumUsers(data) {
var d = data[0];
$("#totalusers").html(d.numusers);
}
function instructorMchoiceModal(data) {
// data.reslist -- student and their answers
// data.answerDict -- answers and count
// data.correct - correct answer
var res = '<table><tr><th>Student</th><th>Answer(s)</th></tr>';
for (var i in data) {
res += '<tr><td>' + data[i][0] + '</td><td>' + data[i][1] + '</td></tr>';
}
res += '</table>';
return res;
}
function compareModal(data, status, whatever) {
var datadict = eval(data)[0];
var answers = datadict.answerDict;
var misc = datadict.misc;
var kl = Object.keys(answers).sort();
var body = '<table>';
body += '<tr><th>Answer</th><th>Percent</th></tr>';
var theClass= '';
for (var k in kl) {
if (kl[k] == misc.correct) {
theClass = 'success';
} else {
theClass = 'info';
}
body += '<tr><td>' + kl[k] + '</td><td class="compare-me-progress">';
pct = answers[kl[k]] + '%';
body += '<div class="progress">';
body += ' <div class="progress-bar progress-bar-' + theClass + '" style="width:'+pct+';">' + pct;
body += ' </div>';
body += '</div></td></tr>';
}
body += '</table>';
if (misc['yourpct'] !== 'unavailable') {
body += '<br /><p>You have ' + misc['yourpct'] + '% correct for all questions</p>';
}
if (datadict.reslist !== undefined) {
body += instructorMchoiceModal(datadict.reslist);
}
var html = '<div class="modal fade">' +
' <div class="modal-dialog compare-modal">' +
' <div class="modal-content">' +
' <div class="modal-header">' +
' <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>' +
' <h4 class="modal-title">Distribution of Answers</h4>' +
' </div>' +
' <div class="modal-body">' +
body +
' </div>' +
' </div>' +
' </div>' +
'</div>';
el = $(html);
el.modal();
}
function compareAnswers(div_id) {
data = {};
data.div_id = div_id;
data.course = eBookConfig.course;
jQuery.get(eBookConfig.ajaxURL + 'getaggregateresults', data, compareModal);
}
function compareFITB(data, status, whatever) {
var answers = eval(data)[0];
var misc = eval(data)[1];
var body = '<table>';
body += '<tr><th>Answer</th><th>Count</th></tr>';
for (var row in answers) {
body += '<tr><td>' + answers[row].answer + '</td><td>' + answers[row].count + ' times</td></tr>';
}
body += '</table>';
if (misc['yourpct'] !== 'unavailable') {
body += '<br /><p>You have ' + misc['yourpct'] + '% correct for all questions</p>';
}
var html = '<div class="modal fade">' +
' <div class="modal-dialog compare-modal">' +
' <div class="modal-content">' +
' <div class="modal-header">' +
' <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>' +
' <h4 class="modal-title">Top Answers</h4>' +
' </div>' +
' <div class="modal-body">' +
body +
' </div>' +
' </div>' +
' </div>' +
'</div>';
el = $(html);
el.modal();
}
function compareFITBAnswers(div_id) {
data = {};
data.div_id = div_id;
data.course = eBookConfig.course;
jQuery.get(eBookConfig.ajaxURL + 'gettop10Answers', data, compareFITB);
}
/* Listen for changes to/addition of a div containing unittest results
(which are generated by Skulpt) and add some nice styles to it*/
function styleUnittestResults() {
$(document).on("DOMNodeInserted", '.unittest-results', function (ev) {
// select the target node
var unittest_results_target = ev.target;
// create an observer instance
var observer = new MutationObserver(function(mutations) {
$(mutations).each(function() {
if (this.type == "attributes") {
var target = $(this.target);
// apply the .alert classes
if(target.text().indexOf("Fail") === -1) {
target.removeClass('alert-danger');
target.addClass('alert alert-success');
} else if (target.text().indexOf('Fail') >= 0) {
target.removeClass('alert-success');
target.addClass('alert alert-danger');
}
// add the progress bar indicating the percent of tests passed
var paragraph = target.find('p');
var result_text = paragraph.text().split(" ");
var pct = '';
$(result_text).each(function() {
if (this.indexOf("%") !== -1) {
pct = this;
var html = 'You passed:' +
'<div class="progress unittest-results-progress">';
if (pct == '100.0%') {
html += ' <div class="progress-bar progress-bar-success" style="width:'+pct+';">';
} else {
html += ' <div class="progress-bar progress-bar-warning" style="width:'+pct+';">';
}
html += pct +
' </div>' +
'</div>';
paragraph.html(html);
}
});
}
});
});
// configuration of the observer:
var config = { attributes: true,
attributeFilter: ['style'],
childList: true,
characterData: true,
subtree: false };
// pass in the target node, as well as the observer options
observer.observe($(unittest_results_target).get(0), config);
});
}
function createScratchActivecode() {
/* set up the scratch Activecode editor in the search menu */
// use the URL to assign a divid - each page should have a unique Activecode block id.
// Remove everything from the URL but the course and page name
var divid = document.URL.split('#')[0];
if (divid.indexOf('static') > 0 ) {
divid = divid.split('static')[1];
} else {
// divid = divid.split('//')[1];
divid = divid.split('?')[0]; // remove any query string (e.g ?lastPosition)
}
divid = divid.replaceAll(':','')
divid = divid.replaceAll('/', '').replace('.html', '');
divid = divid.replaceAll('.','')
// generate the HTML
var html = '<div id="ac_modal_' + divid + '" class="modal fade">' +
' <div class="modal-dialog scratch-ac-modal">' +
' <div class="modal-content">' +
' <div class="modal-header">' +
' <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>' +
' <h4 class="modal-title">Scratch ActiveCode</h4>' +
' </div> ' +
' <div class="modal-body">' +
' <div id="' + divid + '">' +
' <div id="' + divid + '_code_div" style="display: block">' +
' <textarea cols="50" rows="12" id="' + divid + '_code" class="active_code">\n\n\n\n\n</textarea>' +
' </div>' +
' <p class="ac_caption"><span class="ac_caption_text">Scratch Editor</span> </p>' +
' <button class="btn btn-success" id="' + divid + '_runb" onclick="runit(\'' + divid + '\',this, undefined);">Run</button>' +
' <div id="cont"></div>' +
' <button class="ac_opt btn btn-default" style="display: inline-block" id="' + divid + '_saveb" onclick="saveEditor(\'' + divid + '\');">Save</button>' +
' <button class="ac_opt btn btn-default" style="display: inline-block" id="' + divid + '_loadb" onclick="requestCode(\'' + divid + '\');">Load</button>' +
' <div style="text-align: center">' +
' <canvas id="' + divid + '_canvas" class="ac-canvas" height="400" width="400" style="border-style: solid; display: none; text-align: center"></canvas>' +
' </div>' +
' <pre id="' + divid + '_suffix" style="display:none">' +
' </pre>' +
' <pre id="' + divid + '_pre" class="active_out">' +
' </pre>' +
' </div>' +
' </div>' +
' </div>' +
' </div>' +
'</div>';
el = $(html);
$('body').append(el);
el.on('shown.bs.modal show.bs.modal', function () {
el.find('.CodeMirror').each(function (i, e) {
e.CodeMirror.refresh();
e.CodeMirror.focus();
});
});
$(document).bind('keypress', '\\', function(evt) {
toggleScratchActivecode();
return false;
});
}
function toggleScratchActivecode() {
var bforeanchor = document.URL.split('#')[0]
var suffix = 'index'
if (bforeanchor.indexOf('static') > 0) {
suffix = bforeanchor.split('static')[1].split('?')[0].replaceAll('/', '').replace('.html', '');
} else {
suffix = bforeanchor.split('?')[0].replaceAll('/', '').replace('.html', '');
suffix = suffix.replaceAll(':','')
suffix = suffix.replaceAll('.','')
}
var divid = "ac_modal_" + suffix;
var div = $("#" + divid);
div.modal('toggle');
}
function showGradeSummary(data, status, whatever) {
var report = eval(data)[0];
// check for report['message']
if (report['grade']) {
body = "<h4>Grade Report</h4>" +
"<p>This assignment: " + report['grade'] + "</p>" +
"<p>" + report['comment'] + "</p>" +
"<p>Number of graded assignments: " + report['count'] + "</p>" +
"<p>Average score: " + report['avg'] + "</p>"
} else {
body = "<h4>You must be Logged in to see your grade</h4>";
}
var html = '<div class="modal fade">' +
' <div class="modal-dialog compare-modal">' +
' <div class="modal-content">' +
' <div class="modal-header">' +
' <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>' +
' <h4 class="modal-title">Assignment Feedback</h4>' +
' </div>' +
' <div class="modal-body">' +
body +
' </div>' +
' </div>' +
' </div>' +
'</div>';
el = $(html);
el.modal();
}
function createGradeSummary(div_id) {
// get grade and comments for this assignment
// get summary of all grades for this student
// display grades in modal window
var data = {'div_id':div_id}
jQuery.get(eBookConfig.ajaxURL + 'getassignmentgrade', data, showGradeSummary);
}

3192
book/common/js/codemirror.js Normal file

File diff suppressed because it is too large Load diff

237
book/common/js/doctools.js Normal file
View file

@ -0,0 +1,237 @@
/*
* doctools.js
* ~~~~~~~~~~~
*
* Sphinx JavaScript utilities for all documentation.
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/**
* select a different prefix for underscore
*/
$u = _.noConflict();
/**
* make the code below compatible with browsers without
* an installed firebug like debugger
if (!window.console || !console.firebug) {
var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
"dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace",
"profile", "profileEnd"];
window.console = {};
for (var i = 0; i < names.length; ++i)
window.console[names[i]] = function() {};
}
*/
/**
* small helper function to urldecode strings
*/
jQuery.urldecode = function(x) {
return decodeURIComponent(x).replace(/\+/g, ' ');
}
/**
* small helper function to urlencode strings
*/
jQuery.urlencode = encodeURIComponent;
/**
* This function returns the parsed url parameters of the
* current request. Multiple values per key are supported,
* it will always return arrays of strings for the value parts.
*/
jQuery.getQueryParameters = function(s) {
if (typeof s == 'undefined')
s = document.location.search;
var parts = s.substr(s.indexOf('?') + 1).split('&');
var result = {};
for (var i = 0; i < parts.length; i++) {
var tmp = parts[i].split('=', 2);
var key = jQuery.urldecode(tmp[0]);
var value = jQuery.urldecode(tmp[1]);
if (key in result)
result[key].push(value);
else
result[key] = [value];
}
return result;
};
/**
* highlight a given string on a jquery object by wrapping it in
* span elements with the given class name.
*/
jQuery.fn.highlightText = function(text, className) {
function highlight(node) {
if (node.nodeType == 3) {
var val = node.nodeValue;
var pos = val.toLowerCase().indexOf(text);
if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) {
var span = document.createElement("span");
span.className = className;
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
node.parentNode.insertBefore(span, node.parentNode.insertBefore(
document.createTextNode(val.substr(pos + text.length)),
node.nextSibling));
node.nodeValue = val.substr(0, pos);
}
}
else if (!jQuery(node).is("button, select, textarea")) {
jQuery.each(node.childNodes, function() {
highlight(this);
});
}
}
return this.each(function() {
highlight(this);
});
};
/**
* Small JavaScript module for the documentation.
*/
var Documentation = {
init : function() {
this.fixFirefoxAnchorBug();
this.highlightSearchWords();
this.initIndexTable();
},
/**
* i18n support
*/
TRANSLATIONS : {},
PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; },
LOCALE : 'unknown',
// gettext and ngettext don't access this so that the functions
// can safely bound to a different name (_ = Documentation.gettext)
gettext : function(string) {
var translated = Documentation.TRANSLATIONS[string];
if (typeof translated == 'undefined')
return string;
return (typeof translated == 'string') ? translated : translated[0];
},
ngettext : function(singular, plural, n) {
var translated = Documentation.TRANSLATIONS[singular];
if (typeof translated == 'undefined')
return (n == 1) ? singular : plural;
return translated[Documentation.PLURALEXPR(n)];
},
addTranslations : function(catalog) {
for (var key in catalog.messages)
this.TRANSLATIONS[key] = catalog.messages[key];
this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
this.LOCALE = catalog.locale;
},
/**
* add context elements like header anchor links
*/
addContextElements : function() {
$('div[id] > :header:first').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this headline')).
appendTo(this);
});
$('dt[id]').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this definition')).
appendTo(this);
});
},
/**
* workaround a firefox stupidity
*/
fixFirefoxAnchorBug : function() {
if (document.location.hash && $.browser.mozilla)
window.setTimeout(function() {
document.location.href += '';
}, 10);
},
/**
* highlight the search words provided in the url in the text
*/
highlightSearchWords : function() {
var params = $.getQueryParameters();
var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
if (terms.length) {
var body = $('div.body');
window.setTimeout(function() {
$.each(terms, function() {
body.highlightText(this.toLowerCase(), 'highlighted');
});
}, 10);
$('<p class="highlight-link"><a href="javascript:Documentation.' +
'hideSearchWords()">' + _('Hide Search Matches') + '</a></p>')
.appendTo($('#searchbox'));
}
},
/**
* init the domain index toggle buttons
*/
initIndexTable : function() {
var togglers = $('img.toggler').click(function() {
var src = $(this).attr('src');
var idnum = $(this).attr('id').substr(7);
$('tr.cg-' + idnum).toggle();
if (src.substr(-9) == 'minus.png')
$(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
else
$(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
}).css('display', '');
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
togglers.click();
}
},
/**
* helper function to hide the search marks again
*/
hideSearchWords : function() {
$('#searchbox .highlight-link').fadeOut(300);
$('span.highlighted').removeClass('highlighted');
},
/**
* make the url absolute
*/
makeURL : function(relativeURL) {
return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
},
/**
* get the current relative url
*/
getCurrentURL : function() {
var path = document.location.pathname;
var parts = path.split(/\//);
$.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
if (this == '..')
parts.pop();
});
var url = parts.join('/');
return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
}
};
// quick alias for translations
_ = Documentation.gettext;
$(document).ready(function() {
Documentation.init();
});

View file

@ -0,0 +1,632 @@
/*
Online Python Tutor
Copyright (C) 2010 Philip J. Guo (philip@pgbovine.net)
https://github.com/pgbovine/OnlinePythonTutor/
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// The Online Python Tutor front-end, which calls the cgi-bin/web_exec.py
// back-end with a string representing the user's script POST['user_script']
// and receives a complete execution trace, which it parses and displays to HTML.
var PythonTutor;
if (! PythonTutor) {
PythonTutor = {};
}
(function () {
function Visualizer(source_code, trace_data, divid) {
this.stdOutElement = "#pyStdout_" + divid;
this.warnOutElement = "#warningOutput_" + divid;
this.errorOutputElement = "#errorOutput_" + divid;
this.vcrControlsDiv = "#vcrControls_" + divid;
this.dataVisElement = "#dataViz_" + divid;
this.outputPaneTable = "#pyOutputPane_" + divid;
this.codeOutputTable = "#pyCodeOutput_" + divid;
this.curTrace = null;
this.curInstr = 0;
this.divid = divid;
this.jmpFirstInstr = $("#jmpFirstInstr_"+divid);
this.jmpLastInstr = $("#jmpLastInstr_"+divid);
this.jmpStepBack = $("#jmpStepBack_"+divid);
this.jmpStepFwd = $("#jmpStepFwd_"+divid);
this.renderPyCodeOutput(source_code);
this.processTrace(trace_data);
$(this.outputPaneTable).show();
var myvis = this;
this.jmpFirstInstr.click(function() {
myvis.curInstr = 0;
myvis.updateOutput();
logBookEvent({'event':'codelens', 'act': 'first', 'div_id':myvis.divid}); // Log the run event
});
this.jmpLastInstr.click(function() {
myvis.curInstr = myvis.curTrace.length - 1;
myvis.updateOutput();
logBookEvent({'event':'codelens', 'act':'last', 'div_id': myvis.divid}); // Log the run event
});
this.jmpStepBack.click(function() {
if (myvis.curInstr > 0) {
myvis.curInstr -= 1;
myvis.updateOutput();
logBookEvent({'event':'codelens', 'act': 'back', 'div_id': myvis.divid}); // Log the run event
}
});
this.jmpStepFwd.click(function() {
if (myvis.curInstr < myvis.curTrace.length - 1) {
myvis.curInstr += 1;
myvis.updateOutput();
logBookEvent({'event':'codelens', 'act':'fwd', 'div_id':myvis.divid}); // Log the run event
}
});
}
Visualizer.prototype.processTrace = function(traceData) {
this.curTrace = traceData;
this.curInstr = 0;
// delete all stale output
$(this.warnOutElement).html('');
$(this.stdOutElement).val('');
if (this.curTrace.length > 0) {
var lastEntry = this.curTrace[this.curTrace.length - 1];
// if there is some sort of error, then JUMP to it so that we can
// immediately alert the user:
// (cgi-bin/pg_logger.py ensures that if there is an uncaught
// exception, then that exception event will be the FINAL
// entry in this.curTrace. a caught exception will appear somewhere in
// the MIDDLE of this.curTrace)
//
// on second thought, let's hold off on that for now
/*
if (lastEntry.event == 'exception' ||
lastEntry.event == 'uncaught_exception') {
// updateOutput should take care of the rest ...
curInstr = this.curTrace.length - 1;
}
*/
if (lastEntry.event == 'instruction_limit_reached') {
this.curTrace.pop() // kill last entry
var warningMsg = lastEntry.exception_msg;
$(this.warnOutElement).html(htmlspecialchars(warningMsg));
}
// as imran suggests, for a (non-error) one-liner, SNIP off the
// first instruction so that we start after the FIRST instruction
// has been executed ...
else if (this.curTrace.length == 2) {
this.curTrace.shift();
}
}
this.updateOutput();
}
Visualizer.prototype.highlightCodeLine = function(curLine, visitedLinesSet, hasError) {
var tbl = $(this.codeOutputTable);
/* colors - see edu-python.css */
var lightYellow = '#F5F798';
var lightLineColor = '#FFFFCC';
var errorColor = '#F87D76';
var visitedLineColor = '#3D58A2';
// reset then set:
tbl.find('td.lineNo').css('color', '');
tbl.find('td.lineNo').css('font-weight', '');
$.each(visitedLinesSet, function(k, v) {
tbl.find('td.lineNo:eq(' + (k - 1) + ')').css('color', visitedLineColor);
tbl.find('td.lineNo:eq(' + (k - 1) + ')').css('font-weight', 'bold');
});
var lineBgCol = lightLineColor;
if (hasError) {
lineBgCol = errorColor;
}
tbl.find('td.cod').css('border-bottom', '1px solid #ffffff');
if (!hasError) {
tbl.find('td.cod:eq(' + (curLine - 1) + ')').css('border-bottom', '1px solid #F87D76')
}
tbl.find('td.cod').css('background-color', '');
tbl.find('td.cod:eq(' + (curLine - 1) + ')').css('background-color', lineBgCol);
}
// relies on this.curTrace and curInstr globals
Visualizer.prototype.updateOutput = function() {
var curEntry = this.curTrace[this.curInstr];
var hasError = false;
// render VCR controls:
var totalInstrs = this.curTrace.length
var vcr = $(this.vcrControlsDiv);
vcr.find("#curInstr_"+this.divid).html(this.curInstr + 1);
vcr.find("#totalInstrs_"+this.divid).html(totalInstrs);
this.jmpFirstInstr.attr("disabled", false);
this.jmpStepBack.attr("disabled", false);
this.jmpStepFwd.attr("disabled", false);
this.jmpLastInstr.attr("disabled", false);
if (this.curInstr == 0) {
this.jmpFirstInstr.attr("disabled", true);
this.jmpStepBack.attr("disabled", true);
}
if (this.curInstr == (totalInstrs - 1)) {
this.jmpLastInstr.attr("disabled", true);
this.jmpStepFwd.attr("disabled", true);
}
// render error (if applicable):
if (curEntry.event == 'exception' ||
curEntry.event == 'uncaught_exception') {
assert(curEntry.exception_msg);
if (curEntry.exception_msg == "Unknown error") {
$(this.errorOutputElement).html('Unknown error: <a id="editCodeLinkOnError" href="#">view code</a> and please<br/>email as a bug report to philip@pgbovine.net');
}
else {
$(this.errorOutputElement).html(htmlspecialchars(curEntry.exception_msg));
}
$(this.errorOutputElement).show();
hasError = true;
}
else {
$(this.errorOutputElement).hide();
}
// render code output:
if (curEntry.line) {
// calculate all lines that have been 'visited'
// by execution up to (but NOT INCLUDING) curInstr:
var visitedLinesSet = {}
for (var i = 0; i < this.curInstr; i++) {
if (this.curTrace[i].line) {
visitedLinesSet[this.curTrace[i].line] = true;
}
}
this.highlightCodeLine(curEntry.line, visitedLinesSet, hasError);
}
// render stdout:
// keep original horizontal scroll level:
// var oldLeft = $(this.stdOutElement).scrollLeft();
// $(this.stdOutElement).val(curEntry.stdout);
// $(this.stdOutElement).scrollLeft(oldLeft);
// // scroll to bottom, tho:
// $(this.stdOutElement).scrollTop($(this.stdOutElement).attr('scrollHeight'));
$(this.stdOutElement).text(curEntry.stdout);
// render data structures:
$(this.dataVisElement).html(''); // CLEAR IT!
// render locals on stack:
if (curEntry.stack_locals != undefined) {
var self = this;
$.each(curEntry.stack_locals, function (i, frame) {
var funcName = htmlspecialchars(frame[0]); // might contain '<' or '>' for weird names like <genexpr>
var localVars = frame[1];
$(self.dataVisElement).append('<div class="vizFrame">Local variables for <span style="font-family: Andale mono, monospace;">' + funcName + '</span>:</div>');
// render locals in alphabetical order for tidiness:
var orderedVarnames = [];
$.each(localVars, function(varname, val) {
orderedVarnames.push(varname);
});
orderedVarnames.sort();
if (orderedVarnames.length > 0) {
$(self.dataVisElement + " .vizFrame:last").append('<br/><table class="frameDataViz"></table>');
var tbl = $(self.outputPaneTable + " table:last");
$.each(orderedVarnames, function(i, varname) {
var val = localVars[varname];
tbl.append('<tr><td class="varname"></td><td class="val"></td></tr>');
var curTr = tbl.find('tr:last');
if (varname == '__return__') {
curTr.find("td.varname").html('<span style="font-size: 10pt; font-style: italic;">return value</span>');
}
else {
curTr.find("td.varname").html(varname);
}
self.renderData(val, curTr.find("td.val"));
});
tbl.find("tr:last").find("td.varname").css('border-bottom', '0px');
tbl.find("tr:last").find("td.val").css('border-bottom', '0px');
}
else {
$(this.dataVisElement + " .vizFrame:last").append(' <i>none</i>');
}
});
}
// render globals LAST:
$(this.dataVisElement).append('<div class="vizFrame">Global variables:</div>');
var nonEmptyGlobals = false;
var curGlobalFields = {};
if (curEntry.globals != undefined) {
$.each(curEntry.globals, function(varname, val) {
curGlobalFields[varname] = true;
nonEmptyGlobals = true;
});
}
if (nonEmptyGlobals) {
$(this.dataVisElement + " .vizFrame:last").append('<br/><table class="frameDataViz"></table>');
// render all global variables IN THE ORDER they were created by the program,
// in order to ensure continuity:
var orderedGlobals = []
// iterating over ALL instructions (could be SLOW if not for our optimization below)
for (var i = 0; i <= this.curInstr; i++) {
// some entries (like for exceptions) don't have GLOBALS
if (this.curTrace[i].globals == undefined) continue;
$.each(this.curTrace[i].globals, function(varname, val) {
// eliminate duplicates (act as an ordered set)
if ($.inArray(varname, orderedGlobals) == -1) {
orderedGlobals.push(varname);
curGlobalFields[varname] = undefined; // 'unset it'
}
});
var earlyStop = true;
// as an optimization, STOP as soon as you've found everything in curGlobalFields:
for (o in curGlobalFields) {
if (curGlobalFields[o] != undefined) {
earlyStop = false;
break;
}
}
if (earlyStop) {
break;
}
}
var tbl = $(this.outputPaneTable + " table:last");
var self = this;
// iterate IN ORDER (it's possible that not all vars are in curEntry.globals)
$.each(orderedGlobals, function(i, varname) {
var val = curEntry.globals[varname];
if (val != undefined) { // might not be defined at this line, which is OKAY!
tbl.append('<tr><td class="varname"></td><td class="val"></td></tr>');
var curTr = tbl.find('tr:last');
curTr.find("td.varname").html(varname);
self.renderData(val, curTr.find("td.val"));
}
});
tbl.find("tr:last").find("td.varname").css('border-bottom', '0px');
tbl.find("tr:last").find("td.val").css('border-bottom', '0px');
}
else {
$(this.dataVisElement + " .vizFrame:last").append(' <i>none</i>');
}
}
// render the JS data object obj inside of jDomElt,
// which is a jQuery wrapped DOM object
// (obj is in a format encoded by cgi-bin/pg_encoder.py)
Visualizer.prototype.renderData = function(obj, jDomElt) {
// dispatch on types:
var typ = typeof obj;
if (obj == null) {
jDomElt.append('<span class="nullObj">None</span>');
}
else if (typ == "number") {
jDomElt.append('<span class="numberObj">' + obj + '</span>');
}
else if (typ == "boolean") {
if (obj) {
jDomElt.append('<span class="boolObj">True</span>');
}
else {
jDomElt.append('<span class="boolObj">False</span>');
}
}
else if (typ == "string") {
// escape using htmlspecialchars to prevent HTML/script injection
// print as a JSON literal
var literalStr = htmlspecialchars(obj);
literalStr = literalStr.replace('\"', '\\"');
literalStr = '"' + literalStr + '"';
jDomElt.append('<span class="stringObj">' + literalStr + '</span>');
}
else if (typ == "object") {
assert($.isArray(obj));
if (obj[0] == 'LIST') {
assert(obj.length >= 2);
if (obj.length == 2) {
jDomElt.append('<div class="typeLabel">empty list (id=' + obj[1] + ')</div>');
}
else {
jDomElt.append('<div class="typeLabel">list (id=' + obj[1] + '):</div>');
jDomElt.append('<table class="listTbl"><tr></tr><tr></tr></table>');
var tbl = jDomElt.children('table');
var headerTr = tbl.find('tr:first');
var contentTr = tbl.find('tr:last');
var self = this;
jQuery.each(obj, function(ind, val) {
if (ind < 2) return; // skip 'LIST' tag and ID entry
// add a new column and then pass in that newly-added column
// as jDomElt to the recursive call to child:
headerTr.append('<td class="listHeader"></td>');
headerTr.find('td:last').append(ind - 2);
contentTr.append('<td class="listElt"></td>');
self.renderData(val, contentTr.find('td:last'));
});
}
}
else if (obj[0] == 'TUPLE') {
assert(obj.length >= 2);
if (obj.length == 2) {
jDomElt.append('<div class="typeLabel">empty tuple (id=' + obj[1] + ')</div>');
}
else {
jDomElt.append('<div class="typeLabel">tuple (id=' + obj[1] + '):</div>');
jDomElt.append('<table class="tupleTbl"><tr></tr><tr></tr></table>');
var tbl = jDomElt.children('table');
var headerTr = tbl.find('tr:first');
var contentTr = tbl.find('tr:last');
var self = this;
jQuery.each(obj, function(ind, val) {
if (ind < 2) return; // skip 'TUPLE' tag and ID entry
// add a new column and then pass in that newly-added column
// as jDomElt to the recursive call to child:
headerTr.append('<td class="tupleHeader"></td>');
headerTr.find('td:last').append(ind - 2);
contentTr.append('<td class="tupleElt"></td>');
self.renderData(val, contentTr.find('td:last'));
});
}
}
else if (obj[0] == 'SET') {
assert(obj.length >= 2);
if (obj.length == 2) {
jDomElt.append('<div class="typeLabel">empty set (id=' + obj[1] + ')</div>');
}
else {
jDomElt.append('<div class="typeLabel">set (id=' + obj[1] + '):</div>');
jDomElt.append('<table class="setTbl"></table>');
var tbl = jDomElt.children('table');
// create an R x C matrix:
var numElts = obj.length - 2;
// gives roughly a 3x5 rectangular ratio, square is too, err,
// 'square' and boring
var numRows = Math.round(Math.sqrt(numElts));
if (numRows > 3) {
numRows -= 1;
}
var numCols = Math.round(numElts / numRows);
// round up if not a perfect multiple:
if (numElts % numRows) {
numCols += 1;
}
var self = this;
jQuery.each(obj, function(ind, val) {
if (ind < 2) return; // skip 'SET' tag and ID entry
if (((ind - 2) % numCols) == 0) {
tbl.append('<tr></tr>');
}
var curTr = tbl.find('tr:last');
curTr.append('<td class="setElt"></td>');
self.renderData(val, curTr.find('td:last'));
});
}
}
else if (obj[0] == 'DICT') {
assert(obj.length >= 2);
if (obj.length == 2) {
jDomElt.append('<div class="typeLabel">empty dict (id=' + obj[1] + ')</div>');
}
else {
jDomElt.append('<div class="typeLabel">dict (id=' + obj[1] + '):</div>');
jDomElt.append('<table class="dictTbl"></table>');
var tbl = jDomElt.children('table');
var self = this;
$.each(obj, function(ind, kvPair) {
if (ind < 2) return; // skip 'DICT' tag and ID entry
tbl.append('<tr class="dictEntry"><td class="dictKey"></td><td class="dictVal"></td></tr>');
var newRow = tbl.find('tr:last');
var keyTd = newRow.find('td:first');
var valTd = newRow.find('td:last');
self.renderData(kvPair[0], keyTd);
self.renderData(kvPair[1], valTd);
});
}
}
else if (obj[0] == 'INSTANCE') {
assert(obj.length >= 3);
jDomElt.append('<div class="typeLabel">' + obj[1] + ' instance (id=' + obj[2] + ')</div>');
if (obj.length > 3) {
jDomElt.append('<table class="instTbl"></table>');
var tbl = jDomElt.children('table');
var self = this;
$.each(obj, function(ind, kvPair) {
if (ind < 3) return; // skip type tag, class name, and ID entry
tbl.append('<tr class="instEntry"><td class="instKey"></td><td class="instVal"></td></tr>');
var newRow = tbl.find('tr:last');
var keyTd = newRow.find('td:first');
var valTd = newRow.find('td:last');
// the keys should always be strings, so render them directly (and without quotes):
assert(typeof kvPair[0] == "string");
var attrnameStr = htmlspecialchars(kvPair[0]);
keyTd.append('<span class="stringObj">' + attrnameStr + '</span>');
// values can be arbitrary objects, so recurse:
self.renderData(kvPair[1], valTd);
});
}
}
else if (obj[0] == 'CLASS') {
assert(obj.length >= 4);
var superclassStr = '';
if (obj[3].length > 0) {
superclassStr += ('[extends ' + obj[3].join(',') + '] ');
}
jDomElt.append('<div class="typeLabel">' + obj[1] + ' class ' + superclassStr + '(id=' + obj[2] + ')</div>');
if (obj.length > 4) {
jDomElt.append('<table class="classTbl"></table>');
var tbl = jDomElt.children('table');
var self = this;
$.each(obj, function(ind, kvPair) {
if (ind < 4) return; // skip type tag, class name, ID, and superclasses entries
tbl.append('<tr class="classEntry"><td class="classKey"></td><td class="classVal"></td></tr>');
var newRow = tbl.find('tr:last');
var keyTd = newRow.find('td:first');
var valTd = newRow.find('td:last');
// the keys should always be strings, so render them directly (and without quotes):
assert(typeof kvPair[0] == "string");
var attrnameStr = htmlspecialchars(kvPair[0]);
keyTd.append('<span class="stringObj">' + attrnameStr + '</span>');
// values can be arbitrary objects, so recurse:
self.renderData(kvPair[1], valTd);
});
}
}
else if (obj[0] == 'CIRCULAR_REF') {
assert(obj.length == 2);
jDomElt.append('<div class="circRefLabel">circular reference to id=' + obj[1] + '</div>');
}
else {
// render custom data type
assert(obj.length == 3);
typeName = obj[0];
id = obj[1];
strRepr = obj[2];
// if obj[2] is like '<generator object <genexpr> at 0x84760>',
// then display an abbreviated version rather than the gory details
noStrReprRE = /<.* at 0x.*>/;
if (noStrReprRE.test(strRepr)) {
jDomElt.append('<span class="customObj">' + typeName + ' (id=' + id + ')</span>');
}
else {
strRepr = htmlspecialchars(strRepr); // escape strings!
// warning: we're overloading tuple elts for custom data types
jDomElt.append('<div class="typeLabel">' + typeName + ' (id=' + id + '):</div>');
jDomElt.append('<table class="tupleTbl"><tr><td class="tupleElt">' + strRepr + '</td></tr></table>');
}
}
}
else {
alert("Error: renderData FAIL!");
}
}
Visualizer.prototype.renderPyCodeOutput = function(codeStr) {
var tbl = $(this.codeOutputTable);
tbl.html('');
var lines = codeStr.rtrim().split('\n');
$.each(lines, function(i, cod) {
var lineNo = i + 1;
var htmlCod = htmlspecialchars(cod);
tbl.append('<tr><td class="lineNo"></td><td class="cod"></td></tr>');
var curRow = tbl.find('tr:last');
curRow.find('td.lineNo').html(lineNo);
curRow.find('td.cod').html(htmlCod);
});
}
PythonTutor.Visualizer = Visualizer;
})();
String.prototype.rtrim = function() {
return this.replace(/\s*$/g, "");
}
function assert(cond) {
if (!cond) {
alert("Error: ASSERTION FAILED");
}
}
// taken from http://www.toao.net/32-my-htmlspecialchars-function-for-javascript
function htmlspecialchars(str) {
if (typeof(str) == "string") {
str = str.replace(/&/g, "&amp;"); /* must do &amp; first */
// ignore these for now ...
//str = str.replace(/"/g, "&quot;");
//str = str.replace(/'/g, "&#039;");
str = str.replace(/</g, "&lt;");
str = str.replace(/>/g, "&gt;");
// replace spaces:
str = str.replace(/ /g, "&nbsp;");
}
return str;
}

View file

@ -0,0 +1,594 @@
/**
* guiders.js
*
* version 1.3.0
*
* Developed at Optimizely. (www.optimizely.com)
* We make A/B testing you'll actually use.
*
* Released under the Apache License 2.0.
* www.apache.org/licenses/LICENSE-2.0.html
*
* Questions about Guiders?
* You may email me (Jeff Pickhardt) at pickhardt+guiders@gmail.com
*
* Questions about Optimizely should be sent to:
* sales@optimizely.com or support@optimizely.com
*
* Enjoy!
*/
var guiders = (function($) {
var guiders = {};
guiders.version = "1.3.0";
guiders._defaultSettings = {
attachTo: null, // Selector of the element to attach to.
autoFocus: true, // Determines whether or not the browser scrolls to the element.
buttons: [{name: "Next"}, {name: "Back"}],
buttonCustomHTML: "",
classString: null,
closeOnEscape: true,
description: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
highlight: null,
isHashable: true,
offset: {
top: null,
left: null
},
onClose: null,
onHide: null,
onShow: null,
overlay: false,
position: 6, // 1-12 follows an analog clock, 0 means centered.
shouldSkip: function() {}, // Optional handler allows you to skip a guider if returns true.
title: "Sample title goes here",
width: 250,
xButton: true // This places a closer "x" button in the top right of the guider.
};
guiders._htmlSkeleton = [
"<div class='guider'>",
" <div class='guiders_content'>",
" <h1 class='guiders_title'></h1>",
" <div class='guiders_close'></div>",
" <p class='guiders_description'></p>",
" <div class='guiders_buttons_container' style='margin-bottom: 10px; text-align: right;'>",
" </div>",
" </div>",
" <div class='guiders_arrow'>",
" </div>",
"</div>"
].join("");
guiders._arrowSize = 42; // This is the arrow's width and height.
guiders._backButtonTitle = "Back";
guiders._buttonAttributes = {"href": "javascript:void(0);", "style": "margin-right: 8px;"};
guiders._buttonClassName = "btn btn-sm btn-success"; // Override this if you use a different class name for your buttons.
guiders._buttonClickEvent = "click touch"; // Using click touch allows this to trigger with iPad/iPhone taps, as well as browser clicks
guiders._buttonElement = "<a></a>"; // Override this if you want to use a different element for your buttons, like spans.
guiders._closeButtonTitle = "Close";
guiders._currentGuiderID = null;
guiders._fixedOrAbsolute = "fixed";
guiders._guiders = {};
guiders._lastCreatedGuiderID = null;
guiders._nextButtonTitle = "Next";
guiders._offsetNameMapping = {
"topLeft": 11,
"top": 12,
"topRight": 1,
"rightTop": 2,
"right": 3,
"rightBottom": 4,
"bottomRight": 5,
"bottom": 6,
"bottomLeft": 7,
"leftBottom": 8,
"left": 9,
"leftTop": 10
};
guiders._windowHeight = 0;
// Basic IE browser detection
var ieBrowserMatch = navigator.userAgent.match('MSIE (.)');
guiders._isIE = ieBrowserMatch && ieBrowserMatch.length > 1;
guiders._ieVersion = ieBrowserMatch && ieBrowserMatch.length > 1 ? Number(ieBrowserMatch[1]) : -1;
guiders._addButtons = function(myGuider) {
var guiderButtonsContainer = myGuider.elem.find(".guiders_buttons_container");
if (myGuider.buttons === null || myGuider.buttons.length === 0) {
guiderButtonsContainer.remove();
return;
}
for (var i = myGuider.buttons.length - 1; i >= 0; i--) {
var thisButton = myGuider.buttons[i];
var thisButtonElem = $(guiders._buttonElement,
$.extend({"class" : guiders._buttonClassName, "html" : thisButton.name }, guiders._buttonAttributes, thisButton.html || {})
);
if (typeof thisButton.classString !== "undefined" && thisButton.classString !== null) {
thisButtonElem.addClass(thisButton.classString);
}
guiderButtonsContainer.append(thisButtonElem);
var thisButtonName = thisButton.name.toLowerCase();
if (thisButton.onclick) {
thisButtonElem.bind(guiders._buttonClickEvent, thisButton.onclick);
} else {
switch (thisButtonName) {
case guiders._closeButtonTitle.toLowerCase():
thisButtonElem.bind(guiders._buttonClickEvent, function () {
guiders.hideAll();
if (myGuider.onClose) {
myGuider.onClose(myGuider, false /* close by button */);
}
});
break;
case guiders._nextButtonTitle.toLowerCase():
thisButtonElem.bind(guiders._buttonClickEvent, function () {
!myGuider.elem.data("locked") && guiders.next();
});
break;
case guiders._backButtonTitle.toLowerCase():
thisButtonElem.bind(guiders._buttonClickEvent, function () {
!myGuider.elem.data("locked") && guiders.prev();
});
break;
}
}
}
if (myGuider.buttonCustomHTML !== "") {
var myCustomHTML = $(myGuider.buttonCustomHTML);
myGuider.elem.find(".guiders_buttons_container").append(myCustomHTML);
}
if (myGuider.buttons.length === 0) {
guiderButtonsContainer.remove();
}
};
guiders._addXButton = function(myGuider) {
var xButtonContainer = myGuider.elem.find(".guiders_close");
var xButton = $("<div></div>", {
"class" : "guiders_x_button",
"role" : "button"
});
xButtonContainer.append(xButton);
xButton.click(function() {
guiders.hideAll();
if (myGuider.onClose) {
myGuider.onClose(myGuider, true);
}
});
};
guiders._wireEscape = function (myGuider) {
$(document).keydown(function(event) {
if (event.keyCode == 27 || event.which == 27) {
guiders.hideAll();
if (myGuider.onClose) {
myGuider.onClose(myGuider, true /*close by X/Escape*/);
}
return false;
}
});
};
guiders._unWireEscape = function (myGuider) {
$(document).unbind("keydown");
};
guiders._attach = function(myGuider) {
if (typeof myGuider !== 'object') {
return;
}
var attachTo = $(myGuider.attachTo);
var myHeight = myGuider.elem.innerHeight();
var myWidth = myGuider.elem.innerWidth();
if (myGuider.position === 0 || attachTo.length === 0) {
var fixedOrAbsolute = "fixed";
if (guiders._isIE && guiders._ieVersion < 9) {
fixedOrAbsolute = "absolute";
}
myGuider.elem.css("position", fixedOrAbsolute);
myGuider.elem.css("top", ($(window).height() - myHeight) / 3 + "px");
myGuider.elem.css("left", ($(window).width() - myWidth) / 2 + "px");
return;
}
// Otherwise, the guider is positioned relative to the attachTo element.
var base = attachTo.offset();
var top = base.top;
var left = base.left;
// topMarginOfBody corrects positioning if body has a top margin set on it.
var topMarginOfBody = $("body").outerHeight(true) - $("body").outerHeight(false);
top -= topMarginOfBody;
// Now, take into account how the guider should be positioned relative to the attachTo element.
// e.g. top left, bottom center, etc.
if (guiders._offsetNameMapping[myGuider.position]) {
// As an alternative to the clock model, you can also use keywords to position the guider.
myGuider.position = guiders._offsetNameMapping[myGuider.position];
}
var attachToHeight = attachTo.innerHeight();
var attachToWidth = attachTo.innerWidth();
var bufferOffset = 0.9 * guiders._arrowSize;
// offsetMap follows the form: [height, width]
var offsetMap = {
1: [-bufferOffset - myHeight, attachToWidth - myWidth],
2: [0, bufferOffset + attachToWidth],
3: [attachToHeight/2 - myHeight/2, bufferOffset + attachToWidth],
4: [attachToHeight - myHeight, bufferOffset + attachToWidth],
5: [bufferOffset + attachToHeight, attachToWidth - myWidth],
6: [bufferOffset + attachToHeight, attachToWidth/2 - myWidth/2],
7: [bufferOffset + attachToHeight, 0],
8: [attachToHeight - myHeight, -myWidth - bufferOffset],
9: [attachToHeight/2 - myHeight/2, -myWidth - bufferOffset],
10: [0, -myWidth - bufferOffset],
11: [-bufferOffset - myHeight, 0],
12: [-bufferOffset - myHeight, attachToWidth/2 - myWidth/2]
};
var offset = offsetMap[myGuider.position];
top += offset[0];
left += offset[1];
var positionType = "absolute";
// If the element you are attaching to is position: fixed, then we will make the guider
// position: fixed as well.
if (attachTo.css("position") === "fixed" && guiders._fixedOrAbsolute === "fixed") {
positionType = "fixed";
top -= $(window).scrollTop();
left -= $(window).scrollLeft();
}
// If you specify an additional offset parameter when you create the guider, it gets added here.
if (myGuider.offset.top !== null) {
top += myGuider.offset.top;
}
if (myGuider.offset.left !== null) {
left += myGuider.offset.left;
}
guiders._styleArrow(myGuider);
// Finally, set the style of the guider and return it!
myGuider.elem.css({
"position": positionType,
"top": top,
"left": left
});
return myGuider;
};
guiders._guiderById = function(id) {
if (typeof guiders._guiders[id] === "undefined") {
throw "Cannot find guider with id " + id;
}
return guiders._guiders[id];
};
guiders._showOverlay = function() {
// This callback is needed to fix an IE opacity bug.
// See also:
// http://www.kevinleary.net/jquery-fadein-fadeout-problems-in-internet-explorer/
$("#guiders_overlay").fadeIn("fast", function(){
if (this.style.removeAttribute) {
this.style.removeAttribute("filter");
}
});
if (guiders._isIE) {
$("#guiders_overlay").css("position", "absolute");
}
};
guiders._highlightElement = function(selector) {
$(selector).addClass('guiders_highlight');
};
guiders._dehighlightElement = function(selector) {
$(selector).removeClass('guiders_highlight');
};
guiders._hideOverlay = function() {
$("#guiders_overlay").fadeOut("fast");
};
guiders._initializeOverlay = function() {
if ($("#guiders_overlay").length === 0) {
$("<div id='guiders_overlay'></div>").hide().appendTo("body");
}
};
guiders._styleArrow = function(myGuider) {
var position = myGuider.position || 0;
if (!position) {
return;
}
var myGuiderArrow = $(myGuider.elem.find(".guiders_arrow"));
var newClass = {
1: "guiders_arrow_down",
2: "guiders_arrow_left",
3: "guiders_arrow_left",
4: "guiders_arrow_left",
5: "guiders_arrow_up",
6: "guiders_arrow_up",
7: "guiders_arrow_up",
8: "guiders_arrow_right",
9: "guiders_arrow_right",
10: "guiders_arrow_right",
11: "guiders_arrow_down",
12: "guiders_arrow_down"
};
myGuiderArrow.addClass(newClass[position]);
var myHeight = myGuider.elem.innerHeight();
var myWidth = myGuider.elem.innerWidth();
var arrowOffset = guiders._arrowSize / 2;
var positionMap = {
1: ["right", arrowOffset],
2: ["top", arrowOffset],
3: ["top", myHeight/2 - arrowOffset],
4: ["bottom", arrowOffset],
5: ["right", arrowOffset],
6: ["left", myWidth/2 - arrowOffset],
7: ["left", arrowOffset],
8: ["bottom", arrowOffset],
9: ["top", myHeight/2 - arrowOffset],
10: ["top", arrowOffset],
11: ["left", arrowOffset],
12: ["left", myWidth/2 - arrowOffset]
};
var position = positionMap[myGuider.position];
myGuiderArrow.css(position[0], position[1] + "px");
};
/**
* One way to show a guider to new users is to direct new users to a URL such as
* http://www.mysite.com/myapp#guider=welcome
*
* This can also be used to run guiders on multiple pages, by redirecting from
* one page to another, with the guider id in the hash tag.
*
* Alternatively, if you use a session variable or flash messages after sign up,
* you can add selectively add JavaScript to the page: "guiders.show('first');"
*/
guiders._showIfHashed = function(myGuider) {
var GUIDER_HASH_TAG = "guider=";
var hashIndex = window.location.hash.indexOf(GUIDER_HASH_TAG);
if (hashIndex !== -1) {
var hashGuiderId = window.location.hash.substr(hashIndex + GUIDER_HASH_TAG.length);
if (myGuider.id.toLowerCase() === hashGuiderId.toLowerCase()) {
guiders.show(myGuider.id);
}
}
};
guiders.reposition = function() {
var currentGuider = guiders._guiders[guiders._currentGuiderID];
guiders._attach(currentGuider);
};
guiders.next = function() {
var currentGuider = guiders._guiders[guiders._currentGuiderID];
if (typeof currentGuider === "undefined") {
return;
}
currentGuider.elem.data("locked", true);
var nextGuiderId = currentGuider.next || null;
if (nextGuiderId !== null && nextGuiderId !== "") {
var nextGuider = guiders._guiderById(nextGuiderId);
var omitHidingOverlay = nextGuider.overlay ? true : false;
guiders.hideAll(omitHidingOverlay, true);
if (currentGuider && currentGuider.highlight) {
guiders._dehighlightElement(currentGuider.highlight);
}
if (nextGuider.shouldSkip && nextGuider.shouldSkip()) {
guiders._currentGuiderID = nextGuider.id;
guiders.next();
return;
}
else {
guiders.show(nextGuiderId);
}
}
};
guiders.prev = function () {
var currentGuider = guiders._guiders[guiders._currentGuiderID];
if (typeof currentGuider === "undefined") {
// not what we think it is
return;
}
if (currentGuider.prev === null) {
// no previous to look at
return;
}
var prevGuider = guiders._guiders[currentGuider.prev];
prevGuider.elem.data("locked", true);
// Note we use prevGuider.id as "prevGuider" is _already_ looking at the previous guider
var prevGuiderId = prevGuider.id || null;
if (prevGuiderId !== null && prevGuiderId !== "") {
var myGuider = guiders._guiderById(prevGuiderId);
var omitHidingOverlay = myGuider.overlay ? true : false;
guiders.hideAll(omitHidingOverlay, true);
if (prevGuider && prevGuider.highlight) {
guiders._dehighlightElement(prevGuider.highlight);
}
guiders.show(prevGuiderId);
}
};
guiders.createGuider = function(passedSettings) {
if (passedSettings === null || passedSettings === undefined) {
passedSettings = {};
}
// Extend those settings with passedSettings
myGuider = $.extend({}, guiders._defaultSettings, passedSettings);
myGuider.id = myGuider.id || String(Math.floor(Math.random() * 1000));
var guiderElement = $(guiders._htmlSkeleton);
myGuider.elem = guiderElement;
if (typeof myGuider.classString !== "undefined" && myGuider.classString !== null) {
myGuider.elem.addClass(myGuider.classString);
}
myGuider.elem.css("width", myGuider.width + "px");
var guiderTitleContainer = guiderElement.find(".guiders_title");
guiderTitleContainer.html(myGuider.title);
guiderElement.find(".guiders_description").html(myGuider.description);
guiders._addButtons(myGuider);
if (myGuider.xButton) {
guiders._addXButton(myGuider);
}
guiderElement.hide();
guiderElement.appendTo("body");
guiderElement.attr("id", myGuider.id);
// Ensure myGuider.attachTo is a jQuery element.
if (typeof myGuider.attachTo !== "undefined" && myGuider !== null) {
guiders._attach(myGuider);
}
guiders._initializeOverlay();
guiders._guiders[myGuider.id] = myGuider;
if (guiders._lastCreatedGuiderID != null) {
myGuider.prev = guiders._lastCreatedGuiderID;
}
guiders._lastCreatedGuiderID = myGuider.id;
/**
* If the URL of the current window is of the form
* http://www.myurl.com/mypage.html#guider=id
* then show this guider.
*/
if (myGuider.isHashable) {
guiders._showIfHashed(myGuider);
}
return guiders;
};
guiders.hideAll = function(omitHidingOverlay, next) {
next = next || false;
$(".guider:visible").each(function(index, elem){
var myGuider = guiders._guiderById($(elem).attr('id'));
if (myGuider.onHide) {
myGuider.onHide(myGuider, next);
}
});
$(".guider").fadeOut("fast");
var currentGuider = guiders._guiders[guiders._currentGuiderID];
if (currentGuider && currentGuider.highlight) {
guiders._dehighlightElement(currentGuider.highlight);
}
if (typeof omitHidingOverlay !== "undefined" && omitHidingOverlay === true) {
// do nothing for now
} else {
guiders._hideOverlay();
}
return guiders;
};
guiders.show = function(id) {
if (!id && guiders._lastCreatedGuiderID) {
id = guiders._lastCreatedGuiderID;
}
var myGuider = guiders._guiderById(id);
if (myGuider.overlay) {
guiders._showOverlay();
// if guider is attached to an element, make sure it's visible
if (myGuider.highlight) {
guiders._highlightElement(myGuider.highlight);
}
}
if (myGuider.closeOnEscape) {
guiders._wireEscape(myGuider);
} else {
guiders._unWireEscape(myGuider);
}
// You can use an onShow function to take some action before the guider is shown.
if (myGuider.onShow) {
myGuider.onShow(myGuider);
}
guiders._attach(myGuider);
myGuider.elem.fadeIn("fast").data("locked", false);
guiders._currentGuiderID = id;
var windowHeight = guiders._windowHeight = $(window).height();
var scrollHeight = $(window).scrollTop();
var guiderOffset = myGuider.elem.offset();
var guiderElemHeight = myGuider.elem.height();
var isGuiderBelow = (scrollHeight + windowHeight < guiderOffset.top + guiderElemHeight); /* we will need to scroll down */
var isGuiderAbove = (guiderOffset.top < scrollHeight); /* we will need to scroll up */
if (myGuider.autoFocus && (isGuiderBelow || isGuiderAbove)) {
// Sometimes the browser won't scroll if the person just clicked,
// so let's do this in a setTimeout.
setTimeout(guiders.scrollToCurrent, 10);
}
$(myGuider.elem).trigger("guiders.show");
return guiders;
};
guiders.scrollToCurrent = function() {
var currentGuider = guiders._guiders[guiders._currentGuiderID];
if (typeof currentGuider === "undefined") {
return;
}
var windowHeight = guiders._windowHeight;
var scrollHeight = $(window).scrollTop();
var guiderOffset = currentGuider.elem.offset();
var guiderElemHeight = currentGuider.elem.height();
// Scroll to the guider's position.
var scrollToHeight = Math.round(Math.max(guiderOffset.top + (guiderElemHeight / 2) - (windowHeight / 2), 0));
window.scrollTo(0, scrollToHeight);
};
// Change the bubble position after browser gets resized
var _resizing = undefined;
$(window).resize(function() {
if (typeof(_resizing) !== "undefined") {
clearTimeout(_resizing); // Prevents seizures
}
_resizing = setTimeout(function() {
_resizing = undefined;
if (typeof (guiders) !== "undefined") {
guiders.reposition();
}
}, 20);
});
return guiders;
}).call(this, jQuery);

View file

@ -0,0 +1,630 @@
// TODO actually recognize syntax of TypeScript constructs
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var statementIndent = parserConfig.statementIndent;
var jsonMode = parserConfig.json;
var isTS = parserConfig.typescript;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
var jsKeywords = {
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
"this": kw("this"), "module": kw("module"), "class": kw("class"), "super": kw("atom"),
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C
};
// Extend the 'normal' keywords with the TypeScript language extensions
if (isTS) {
var type = {type: "variable", style: "variable-3"};
var tsKeywords = {
// object-like things
"interface": kw("interface"),
"extends": kw("extends"),
"constructor": kw("constructor"),
// scope modifiers
"public": kw("public"),
"private": kw("private"),
"protected": kw("protected"),
"static": kw("static"),
// types
"string": type, "number": type, "bool": type, "any": type
};
for (var attr in tsKeywords) {
jsKeywords[attr] = tsKeywords[attr];
}
}
return jsKeywords;
}();
var isOperatorChar = /[+\-*&%=<>!?|~^]/;
function readRegexp(stream) {
var escaped = false, next, inSet = false;
while ((next = stream.next()) != null) {
if (!escaped) {
if (next == "/" && !inSet) return;
if (next == "[") inSet = true;
else if (inSet && next == "]") inSet = false;
}
escaped = !escaped && next == "\\";
}
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function tokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
return ret("number", "number");
} else if (ch == "." && stream.match("..")) {
return ret("spread", "meta");
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
return ret(ch);
} else if (ch == "=" && stream.eat(">")) {
return ret("=>", "operator");
} else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
} else if (/\d/.test(ch)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
} else if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
} else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
} else if (state.lastType == "operator" || state.lastType == "keyword c" ||
state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) {
readRegexp(stream);
stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
return ret("regexp", "string-2");
} else {
stream.eatWhile(isOperatorChar);
return ret("operator", "operator", stream.current());
}
} else if (ch == "`") {
state.tokenize = tokenQuasi;
return tokenQuasi(stream, state);
} else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
} else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", "operator", stream.current());
} else {
stream.eatWhile(/[\w\$_]/);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (next == quote && !escaped) break;
escaped = !escaped && next == "\\";
}
if (!escaped) state.tokenize = tokenBase;
return ret("string", "string");
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
function tokenQuasi(stream, state) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
state.tokenize = tokenBase;
break;
}
escaped = !escaped && next == "\\";
}
return ret("quasi", "string-2", stream.current());
}
var brackets = "([{}])";
// This is a crude lookahead trick to try and notice that we're
// parsing the argument patterns for a fat-arrow function before we
// actually hit the arrow token. It only works if the arrow is on
// the same line as the arguments and there's no strange noise
// (comments) in between. Fallback is to only notice when we hit the
// arrow, and not declare the arguments as locals for the arrow
// body.
function findFatArrow(stream, state) {
if (state.fatArrowAt) state.fatArrowAt = null;
var arrow = stream.string.indexOf("=>", stream.start);
if (arrow < 0) return;
var depth = 0, sawSomething = false;
for (var pos = arrow - 1; pos >= 0; --pos) {
var ch = stream.string.charAt(pos);
var bracket = brackets.indexOf(ch);
if (bracket >= 0 && bracket < 3) {
if (!depth) { ++pos; break; }
if (--depth == 0) break;
} else if (bracket >= 3 && bracket < 6) {
++depth;
} else if (/[$\w]/.test(ch)) {
sawSomething = true;
} else if (sawSomething && !depth) {
++pos;
break;
}
}
if (sawSomething && !depth) state.fatArrowAt = pos;
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
for (var cx = state.context; cx; cx = cx.prev) {
for (var v = cx.vars; v; v = v.next)
if (v.name == varname) return true;
}
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function register(varname) {
function inList(list) {
for (var v = list; v; v = v.next)
if (v.name == varname) return true;
return false;
}
var state = cx.state;
if (state.context) {
cx.marked = "def";
if (inList(state.localVars)) return;
state.localVars = {name: varname, next: state.localVars};
} else {
if (inList(state.globalVars)) return;
if (parserConfig.globalVars)
state.globalVars = {name: varname, next: state.globalVars};
}
}
// Combinators
var defaultVars = {name: "this", next: {name: "arguments"}};
function pushcontext() {
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
cx.state.localVars = defaultVars;
}
function popcontext() {
cx.state.localVars = cx.state.context.vars;
cx.state.context = cx.state.context.prev;
}
function pushlex(type, info) {
var result = function() {
var state = cx.state, indent = state.indented;
if (state.lexical.type == "stat") indent = state.lexical.indented;
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
return function(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(arguments.callee);
};
}
function statement(type, value) {
if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex);
if (type == ";") return cont();
if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse);
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
block, poplex, poplex);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
if (type == "module") return cont(pushlex("form"), pushcontext, afterModule, popcontext, poplex);
if (type == "class") return cont(pushlex("form"), className, objlit, poplex);
if (type == "export") return cont(pushlex("form"), afterExport, poplex);
if (type == "import") return cont(pushlex("form"), afterImport, poplex);
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
return expressionInner(type, false);
}
function expressionNoComma(type) {
return expressionInner(type, true);
}
function expressionInner(type, noComma) {
if (cx.state.fatArrowAt == cx.stream.start) {
var body = noComma ? arrowBodyNoComma : arrowBody;
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
}
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
if (type == "function") return cont(functiondef);
if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop);
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeexpressionNoComma(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expressionNoComma);
}
function maybeoperatorComma(type, value) {
if (type == ",") return cont(expression);
return maybeoperatorNoComma(type, value, false);
}
function maybeoperatorNoComma(type, value, noComma) {
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
var expr = noComma == false ? expression : expressionNoComma;
if (value == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "operator") {
if (/\+\+|--/.test(value)) return cont(me);
if (value == "?") return cont(expression, expect(":"), expr);
return cont(expr);
}
if (type == "quasi") { cx.cc.push(me); return quasi(value); }
if (type == ";") return;
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
if (type == ".") return cont(property, me);
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
}
function quasi(value) {
if (value.slice(value.length - 2) != "${") return cont();
return cont(expression, continueQuasi);
}
function continueQuasi(type) {
if (type == "}") {
cx.marked = "string-2";
cx.state.tokenize = tokenQuasi;
return cont();
}
}
function arrowBody(type) {
findFatArrow(cx.stream, cx.state);
if (type == "{") return pass(statement);
return pass(expression);
}
function arrowBodyNoComma(type) {
findFatArrow(cx.stream, cx.state);
if (type == "{") return pass(statement);
return pass(expressionNoComma);
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperatorComma, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type, value) {
if (type == "variable") {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
} else if (type == "number" || type == "string") {
cx.marked = type + " property";
} else if (type == "[") {
return cont(expression, expect("]"), afterprop);
}
if (atomicTypes.hasOwnProperty(type)) return cont(afterprop);
}
function getterSetter(type) {
if (type != "variable") return pass(afterprop);
cx.marked = "property";
return cont(functiondef);
}
function afterprop(type) {
if (type == ":") return cont(expressionNoComma);
if (type == "(") return pass(functiondef);
}
function commasep(what, end) {
function proceed(type) {
if (type == ",") {
var lex = cx.state.lexical;
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
return cont(what, proceed);
}
if (type == end) return cont();
return cont(expect(end));
}
return function(type) {
if (type == end) return cont();
return pass(what, proceed);
};
}
function contCommasep(what, end, info) {
for (var i = 3; i < arguments.length; i++)
cx.cc.push(arguments[i]);
return cont(pushlex(end, info), commasep(what, end), poplex);
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function maybetype(type) {
if (isTS && type == ":") return cont(typedef);
}
function typedef(type) {
if (type == "variable"){cx.marked = "variable-3"; return cont();}
}
function vardef() {
return pass(pattern, maybetype, maybeAssign, vardefCont);
}
function pattern(type, value) {
if (type == "variable") { register(value); return cont(); }
if (type == "[") return contCommasep(pattern, "]");
if (type == "{") return contCommasep(proppattern, "}");
}
function proppattern(type, value) {
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
register(value);
return cont(maybeAssign);
}
if (type == "variable") cx.marked = "property";
return cont(expect(":"), pattern, maybeAssign);
}
function maybeAssign(_type, value) {
if (value == "=") return cont(expressionNoComma);
}
function vardefCont(type) {
if (type == ",") return cont(vardef);
}
function maybeelse(type, value) {
if (type == "keyword b" && value == "else") return cont(pushlex("form"), statement, poplex);
}
function forspec(type) {
if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
}
function forspec1(type) {
if (type == "var") return cont(vardef, expect(";"), forspec2);
if (type == ";") return cont(forspec2);
if (type == "variable") return cont(formaybeinof);
return pass(expression, expect(";"), forspec2);
}
function formaybeinof(_type, value) {
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return cont(maybeoperatorComma, forspec2);
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return pass(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
}
function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext);
}
function funarg(type) {
if (type == "spread") return cont(funarg);
return pass(pattern, maybetype);
}
function className(type, value) {
if (type == "variable") {register(value); return cont(classNameAfter);}
}
function classNameAfter(_type, value) {
if (value == "extends") return cont(expression);
}
function objlit(type) {
if (type == "{") return contCommasep(objprop, "}");
}
function afterModule(type, value) {
if (type == "string") return cont(statement);
if (type == "variable") { register(value); return cont(maybeFrom); }
}
function afterExport(_type, value) {
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
return pass(statement);
}
function afterImport(type) {
if (type == "string") return cont();
return pass(importSpec, maybeFrom);
}
function importSpec(type, value) {
if (type == "{") return contCommasep(importSpec, "}");
if (type == "variable") register(value);
return cont();
}
function maybeFrom(_type, value) {
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
}
function arrayLiteral(type) {
if (type == "]") return cont();
return pass(expressionNoComma, maybeArrayComprehension);
}
function maybeArrayComprehension(type) {
if (type == "for") return pass(comprehension, expect("]"));
if (type == ",") return cont(commasep(expressionNoComma, "]"));
return pass(commasep(expressionNoComma, "]"));
}
function comprehension(type) {
if (type == "for") return cont(forspec, comprehension);
if (type == "if") return cont(expression, comprehension);
}
// Interface
return {
startState: function(basecolumn) {
var state = {
tokenize: tokenBase,
lastType: "sof",
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
context: parserConfig.localVars && {vars: parserConfig.localVars},
indented: 0
};
if (parserConfig.globalVars) state.globalVars = parserConfig.globalVars;
return state;
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
findFatArrow(stream, state);
}
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize == tokenComment) return CodeMirror.Pass;
if (state.tokenize != tokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
// Kludge to prevent 'maybelse' from blocking lexical scope pops
for (var i = state.cc.length - 1; i >= 0; --i) {
var c = state.cc[i];
if (c == poplex) lexical = lexical.prev;
else if (c != maybeelse) break;
}
if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? statementIndent || indentUnit : 0);
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricChars: ":{}",
blockCommentStart: jsonMode ? null : "/*",
blockCommentEnd: jsonMode ? null : "*/",
lineComment: jsonMode ? null : "//",
fold: "brace",
helperType: jsonMode ? "json" : "javascript",
jsonMode: jsonMode
};
});
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("text/ecmascript", "javascript");
CodeMirror.defineMIME("application/javascript", "javascript");
CodeMirror.defineMIME("application/ecmascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });

4
book/common/js/jquery-fix.js vendored Normal file
View file

@ -0,0 +1,4 @@
// The Bootstrap-based Sphinx theme uses $jqTheme
// Setting $ is needed to override the (old!) version of jQuery packaged with Sphinx
var $jqTheme = jQuery.noConflict();
var $ = jQuery.noConflict();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,53 @@
/*
highlight v4
Highlights arbitrary terms.
<http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html>
MIT license.
Johann Burkard
<http://johannburkard.de>
<mailto:jb@eaio.com>
*/
jQuery.fn.highlight = function(pat) {
function innerHighlight(node, pat) {
var skip = 0;
if (node.nodeType == 3) {
var pos = node.data.toUpperCase().indexOf(pat);
if (pos >= 0) {
var spannode = document.createElement('span');
spannode.className = 'highlight';
var middlebit = node.splitText(pos);
var endbit = middlebit.splitText(pat.length);
var middleclone = middlebit.cloneNode(true);
spannode.appendChild(middleclone);
middlebit.parentNode.replaceChild(spannode, middlebit);
skip = 1;
}
}
else if (node.nodeType == 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
for (var i = 0; i < node.childNodes.length; ++i) {
i += innerHighlight(node.childNodes[i], pat);
}
}
return skip;
}
return this.length && pat && pat.length ? this.each(function() {
innerHighlight(this, pat.toUpperCase());
}) : this;
};
jQuery.fn.removeHighlight = function() {
return this.find("span.highlight").each(function() {
this.parentNode.firstChild.nodeName;
with (this.parentNode) {
replaceChild(this.firstChild, this);
normalize();
}
}).end();
};

View file

@ -0,0 +1,15 @@
/*
* jQuery Hotkeys Plugin
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Based upon the plugin by Tzury Bar Yochay:
* http://github.com/tzuryby/hotkeys
*
* Original idea by:
* Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
*/
(function(e){function g(a){"string"===typeof a.data&&(a.data={keys:a.data});if(a.data&&a.data.keys&&"string"===typeof a.data.keys){var g=a.handler,h=a.data.keys.toLowerCase().split(" "),k="text password number email url range date month week time datetime datetime-local search color tel".split(" ");a.handler=function(c){if(this===c.target||!(/textarea|select/i.test(c.target.nodeName)||-1<e.inArray(c.target.type,k))){var d=e.hotkeys.specialKeys[c.keyCode],a="keypress"===c.type&&String.fromCharCode(c.which).toLowerCase(),
b="",f={};c.altKey&&"alt"!==d&&(b+="alt+");c.ctrlKey&&"ctrl"!==d&&(b+="ctrl+");c.metaKey&&(!c.ctrlKey&&"meta"!==d)&&(b+="meta+");c.shiftKey&&"shift"!==d&&(b+="shift+");d&&(f[b+d]=!0);a&&(f[b+a]=!0,f[b+e.hotkeys.shiftNums[a]]=!0,"shift+"===b&&(f[e.hotkeys.shiftNums[a]]=!0));d=0;for(a=h.length;d<a;d++)if(f[h[d]])return g.apply(this,arguments)}}}}e.hotkeys={version:"0.8",specialKeys:{8:"backspace",9:"tab",10:"return",13:"return",16:"shift",17:"ctrl",18:"alt",19:"pause",20:"capslock",27:"esc",32:"space",
33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"del",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scroll",186:";",191:"/",220:"\\",222:"'",224:"meta"},shiftNums:{"`":"~",1:"!",2:"@",3:"#",4:"$",5:"%",6:"^",7:"&",8:"*",9:"(",0:")","-":"_","=":"+",";":": ",
"'":'"',",":"<",".":">","/":"?","\\":"|"}};e.each(["keydown","keyup","keypress"],function(){e.event.special[this]={add:g}})})(this.jQuery);

View file

@ -0,0 +1,262 @@
/*!
* jQuery idleTimer plugin
* version 0.9.100511
* by Paul Irish.
* http://github.com/paulirish/yui-misc/tree/
* MIT license
* adapted from YUI idle timer by nzakas:
* http://github.com/nzakas/yui-misc/
*/
/*
* Copyright (c) 2009 Nicholas C. Zakas
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/* updated to fix Chrome setTimeout issue by Zaid Zawaideh */
// API available in <= v0.8
/*******************************
// idleTimer() takes an optional argument that defines the idle timeout
// timeout is in milliseconds; defaults to 30000
$.idleTimer(10000);
$(document).bind("idle.idleTimer", function(){
// function you want to fire when the user goes idle
});
$(document).bind("active.idleTimer", function(){
// function you want to fire when the user becomes active again
});
// pass the string 'destroy' to stop the timer
$.idleTimer('destroy');
// you can query if the user is idle or not with data()
$.data(document,'idleTimer'); // 'idle' or 'active'
// you can get time elapsed since user when idle/active
$.idleTimer('getElapsedTime'); // time since state change in ms
********/
// API available in >= v0.9
/*************************
// bind to specific elements, allows for multiple timer instances
$(elem).idleTimer(timeout|'destroy'|'getElapsedTime');
$.data(elem,'idleTimer'); // 'idle' or 'active'
// if you're using the old $.idleTimer api, you should not do $(document).idleTimer(...)
// element bound timers will only watch for events inside of them.
// you may just want page-level activity, in which case you may set up
// your timers on document, document.documentElement, and document.body
// You can optionally provide a second argument to override certain options.
// Here are the defaults, so you can omit any or all of them.
$(elem).idleTimer(timeout, {
startImmediately: true, //starts a timeout as soon as the timer is set up; otherwise it waits for the first event.
idle: false, //indicates if the user is idle
enabled: true, //indicates if the idle timer is enabled
events: 'mousemove keydown DOMMouseScroll mousewheel mousedown touchstart touchmove' // activity is one of these events
});
********/
(function($){
$.idleTimer = function(newTimeout, elem, opts){
// defaults that are to be stored as instance props on the elem
opts = $.extend({
startImmediately: true, //starts a timeout as soon as the timer is set up
idle: false, //indicates if the user is idle
enabled: true, //indicates if the idle timer is enabled
timeout: 30000, //the amount of time (ms) before the user is considered idle
events: 'mousemove keydown DOMMouseScroll mousewheel mousedown touchstart touchmove' // activity is one of these events
}, opts);
elem = elem || document;
/* (intentionally not documented)
* Toggles the idle state and fires an appropriate event.
* @return {void}
*/
var toggleIdleState = function(myelem){
// curse you, mozilla setTimeout lateness bug!
if (typeof myelem === 'number'){
myelem = undefined;
}
var obj = $.data(myelem || elem,'idleTimerObj');
//toggle the state
obj.idle = !obj.idle;
// reset timeout
var elapsed = (+new Date()) - obj.olddate;
obj.olddate = +new Date();
// handle Chrome always triggering idle after js alert or comfirm popup
if (obj.idle && (elapsed < opts.timeout)) {
obj.idle = false;
clearTimeout($.idleTimer.tId);
if (opts.enabled)
$.idleTimer.tId = setTimeout(toggleIdleState, opts.timeout);
return;
}
//fire appropriate event
// create a custom event, but first, store the new state on the element
// and then append that string to a namespace
var event = jQuery.Event( $.data(elem,'idleTimer', obj.idle ? "idle" : "active" ) + '.idleTimer' );
// we do want this to bubble, at least as a temporary fix for jQuery 1.7
// event.stopPropagation();
$(elem).trigger(event);
},
/**
* Stops the idle timer. This removes appropriate event handlers
* and cancels any pending timeouts.
* @return {void}
* @method stop
* @static
*/
stop = function(elem){
var obj = $.data(elem,'idleTimerObj') || {};
//set to disabled
obj.enabled = false;
//clear any pending timeouts
clearTimeout(obj.tId);
//detach the event handlers
$(elem).off('.idleTimer');
},
/* (intentionally not documented)
* Handles a user event indicating that the user isn't idle.
* @param {Event} event A DOM2-normalized event object.
* @return {void}
*/
handleUserEvent = function(){
var obj = $.data(this,'idleTimerObj');
//clear any existing timeout
clearTimeout(obj.tId);
//if the idle timer is enabled
if (obj.enabled){
//if it's idle, that means the user is no longer idle
if (obj.idle){
toggleIdleState(this);
}
//set a new timeout
obj.tId = setTimeout(toggleIdleState, obj.timeout);
}
};
/**
* Starts the idle timer. This adds appropriate event handlers
* and starts the first timeout.
* @param {int} newTimeout (Optional) A new value for the timeout period in ms.
* @return {void}
* @method $.idleTimer
* @static
*/
var obj = $.data(elem,'idleTimerObj') || {};
obj.olddate = obj.olddate || +new Date();
//assign a new timeout if necessary
if (typeof newTimeout === "number"){
opts.timeout = newTimeout;
} else if (newTimeout === 'destroy') {
stop(elem);
return this;
} else if (newTimeout === 'getElapsedTime'){
return (+new Date()) - obj.olddate;
}
//assign appropriate event handlers
$(elem).on($.trim((opts.events+' ').split(' ').join('.idleTimer ')),handleUserEvent);
obj.idle = opts.idle;
obj.enabled = opts.enabled;
obj.timeout = opts.timeout;
//set a timeout to toggle state. May wish to omit this in some situations
if (opts.startImmediately) {
obj.tId = setTimeout(toggleIdleState, obj.timeout);
}
// assume the user is active for the first x seconds.
$.data(elem,'idleTimer',"active");
// store our instance on the object
$.data(elem,'idleTimerObj',obj);
}; // end of $.idleTimer()
// v0.9 API for defining multiple timers.
$.fn.idleTimer = function(newTimeout,opts){
// Allow omission of opts for backward compatibility
if (!opts) {
opts = {};
}
if(this[0]){
$.idleTimer(newTimeout,this[0],opts);
}
return this;
};
})(jQuery);

5
book/common/js/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

219
book/common/js/navhelp.js Normal file
View file

@ -0,0 +1,219 @@
/**
* Created by isaacdontjelindell on 8/2/13.
*/
/* Sets up the interactive navhelp */
function setup() {
guiders.createGuider({
buttons: [{name: "Next"}],
attachTo: ".title-link-img",
highlight: ".title-link-img",
overlay: true,
position: 3,
title: "Table of Contents",
description: "Click on the title at any time to see the Table of Contents for this textbook.",
id: "first",
next: "second"
}).show();
guiders.createGuider({
attachTo: ".logo-link-img",
highlight: ".logo-link-img",
overlay: true,
position: 3,
title: "Homepage",
description: "Click on the Runestone Interactive logo to go back to the homepage, where you can see the other textbooks that are available.",
id: "second",
next: "third"
});
guiders.createGuider({
attachTo: ".page-dropdown-img",
highlight: ".page-dropdown-img",
overlay: true,
position: 3,
title: "Page Navigation",
description: "Click here to jump to a section within the current chapter.",
id: "third",
next: "fourth"
});
guiders.createGuider({
attachTo: ".search-dropdown-img",
highlight: ".search-dropdown-img",
overlay: true,
position: 3,
title: "Search Menu",
description: "This menu allows you to search this textbook, as well as open a scratchpad. You can also press the '\\' (backslash) key at any time to open the scratchpad.",
id: "fourth",
next: "fifth"
});
guiders.createGuider({
attachTo: ".user-dropdown-img",
highlight: ".user-dropdown-img",
overlay: true,
position: 3,
title: "Account Menu",
description: "Log in or register here so that you can save and load code you write and save your position in the textbook. Don't worry, it's easy!",
id: "fifth",
next: "sixth"
});
guiders.createGuider({
attachTo: "#codeexample1 .ac_caption",
highlight: "#codeexample1 .ac_caption",
title: "ActiveCode Blocks",
description: "ActiveCode blocks allow you to write and execute Python code right in the textbook.",
id: "sixth",
next: "seventh"
});
guiders.createGuider({
attachTo: "#codeexample1_code_div",
highlight: "#codeexample1_code_div",
overlay: true,
title: "ActiveCode Editor",
description: "Write and edit code in this text window...",
id: "seventh",
next: "eighth"
});
guiders.createGuider({
attachTo: "#codeexample1_runb",
highlight: "#codeexample1_runb",
overlay: true,
position: 3,
title: "ActiveCode Editor",
description: "...and then click the 'Run' button to execute your code.",
id: "eighth",
next: "ninth"
});
guiders.createGuider({
attachTo: "#codeexample1_saveb",
highlight: "#codeexample1_saveb",
overlay: true,
position: 3,
title: "ActiveCode Blocks",
description: "If you are logged in, you can save your code, and then load again later.",
id: "ninth",
next: "tenth"
});
guiders.createGuider({
attachTo: "#firstexample table",
highlight: "#firstexample table",
title: "CodeLens",
description: "The CodeLens visualizer allows you to execute some code step-by-step, and see the values of all the variables and objects as they are executed.",
id: "tenth",
next: "eleventh"
});
guiders.createGuider({
attachTo: "#firstexample #jmpStepFwd",
highlight: "#firstexample #jmpStepFwd",
overlay: true,
title: "CodeLens Controls",
description: "Use these buttons below the code window to control how you step through the code.",
id: "eleventh",
next: "twelfth"
});
guiders.createGuider({
attachTo: "#question1_1",
highlight: "#question1_1",
overlay: true,
title: "Self-Check Questions",
description: "These questions allow you to check your understand as you move through the textbook.",
id: "twelfth",
next: "thirteenth"
});
guiders.createGuider({
attachTo: "#question1_1 button[name='do answer']",
highlight: "#question1_1 button[name='do answer']",
overlay: true,
position: 3,
title: "Self-Check Questions",
description: "Click this button to get feedback on your answer(s).",
id: "thirteenth",
next: "fourteenth"
});
guiders.createGuider({
attachTo: "#question1_1 button[name='compare']",
highlight: "#question1_1 button[name='compare']",
overlay: true,
position: 3,
title: "Self-Check Questions",
description: "Click this button to get see how you are doing in relation to other people using the textbook.",
id: "fourteenth",
next: "fifteenth"
});
guiders.createGuider({
attachTo: ".parsons",
highlight: ".parsons",
overlay: true,
title: "Parsons Problems",
description: "Parsons exercises ask you to arrange lines of code in the correct order.",
id: "fifteenth",
next: "sixteenth"
});
guiders.createGuider({
attachTo: "#parsons-sortableTrash-111",
highlight: "#parsons-sortableTrash-111",
overlay: true,
title: "Parsons Problems",
description: "Drag lines of code from here...",
id: "sixteenth",
next: "seventeenth"
});
guiders.createGuider({
attachTo: "#ul-parsons-sortableCode-111",
highlight: "#ul-parsons-sortableCode-111",
overlay: true,
title: "Parsons Problems",
description: "...to here.",
id: "seventeenth",
next: "eighteenth"
});
guiders.createGuider({
attachTo: "#checkMe111",
highlight: "#checkMe111",
overlay: true,
position: 3,
title: "Parsons Problems",
description: "Click this button to check if you've arranged the code in the correct order.",
id: "eighteenth",
next: "nineteenth"
});
guiders.createGuider({
attachTo: "#embedded-videos img",
highlight: "#embedded-videos img",
overlay: true,
title: "Embedded Videos",
description: "To play a video embedded in the text, just click the play button.",
id: "nineteenth",
next: "twentieth"
});
guiders.createGuider({
buttons: [{name: "Close"}],
attachTo: "body",
position: 0,
overlay: true,
title: "Thank You!",
description: "Thanks for using this interactive textbook. ",
id: "twentieth"
});
}
setup();

View file

@ -0,0 +1,5 @@
// revert the version of underscore and jquery to whatever it was
// before parson's code included header files
var $pjQ = jQuery.noConflict(true);
var _p = _.noConflict();

61
book/common/js/poll.js Normal file
View file

@ -0,0 +1,61 @@
// Javascript needed for the poll Sphinx directive type
function submitPoll(div_id) {
var form = $("#"+div_id+"_poll");
var poll_val = form.find("input:radio[name="+div_id +"_opt]:checked").val();
if(poll_val === undefined)
return;
var poll_comment = form.find("input:text[name="+div_id+"_comment]").val();
var act = '';
if((poll_comment === undefined) || (poll_comment[0] !== undefined))
act = poll_val + ":" + poll_comment;
else
act = poll_val;
var eventInfo = {'event':'poll', 'act':act, 'div_id':div_id};
// log the response to the database
logBookEvent(eventInfo); // in bookfuncs.js
// log the fact that the user has answered the poll to local storage
localStorage.setItem(div_id, "true");
// hide the poll inputs
$("#"+div_id+"_poll_input").hide();
// show the results of the poll
var data = {};
data.div_id = div_id;
data.course = eBookConfig.course;
jQuery.get(eBookConfig.ajaxURL+'getpollresults', data, showPollResults);
}
function showPollResults(data) {
// create the display of the poll results
results = eval(data);
var total = results[0];
var opt_list = results[1];
var count_list = results[2];
var div_id = results[3];
var result_div = $("#"+div_id+"_results");
result_div.html("<b>Results:</b><br><br>");
result_div.show();
var list = $(document.createElement("ol"));
for(var i=0; i<opt_list.length; i++) {
var count = count_list[i];
var percent = (count / total) * 100;
var text = Math.round(10*percent)/10 + "%"; // round percent to 10ths
var html = "<li value='"+opt_list[i]+"'><div class='progress'><div class='progress-bar progress-bar-success' style='width:"+percent+"%;'><span class='poll-text'>"+text+"</span></div></div></li>";
var el = $(html);
list.append(el);
}
result_div.append(list);
}

Some files were not shown because too many files have changed in this diff Show more