first import book
This commit is contained in:
parent
6be2701fd8
commit
dfd9c869d5
30 changed files with 47797 additions and 0 deletions
468
book/common/js/activecode.js
Normal file
468
book/common/js/activecode.js
Normal 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);
|
||||
}
|
||||
};
|
||||
204
book/common/js/animationbase.js
Normal file
204
book/common/js/animationbase.js
Normal 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
366
book/common/js/assess.js
Normal 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
950
book/common/js/bookfuncs.js
Normal 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, "<").replace(/>/g, ">").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">×</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">×</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">×</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">×</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
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
237
book/common/js/doctools.js
Normal 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();
|
||||
});
|
||||
632
book/common/js/edu-python.js
Normal file
632
book/common/js/edu-python.js
Normal 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, "&"); /* must do & first */
|
||||
|
||||
// ignore these for now ...
|
||||
//str = str.replace(/"/g, """);
|
||||
//str = str.replace(/'/g, "'");
|
||||
|
||||
str = str.replace(/</g, "<");
|
||||
str = str.replace(/>/g, ">");
|
||||
|
||||
// replace spaces:
|
||||
str = str.replace(/ /g, " ");
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
594
book/common/js/guiders-1.3.0.js
Normal file
594
book/common/js/guiders-1.3.0.js
Normal 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);
|
||||
630
book/common/js/javascript.js
Normal file
630
book/common/js/javascript.js
Normal 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
4
book/common/js/jquery-fix.js
vendored
Normal 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();
|
||||
2
book/common/js/jquery-migrate-1.2.1.min.js
vendored
Normal file
2
book/common/js/jquery-migrate-1.2.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
book/common/js/jquery-ui-1.10.3.custom.min.js
vendored
Normal file
7
book/common/js/jquery-ui-1.10.3.custom.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
53
book/common/js/jquery.highlight.js
Normal file
53
book/common/js/jquery.highlight.js
Normal 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();
|
||||
};
|
||||
15
book/common/js/jquery.hotkey.js
Normal file
15
book/common/js/jquery.hotkey.js
Normal 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);
|
||||
262
book/common/js/jquery.idle-timer.js
Normal file
262
book/common/js/jquery.idle-timer.js
Normal 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
5
book/common/js/jquery.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1031
book/common/js/jquery.tablesorter.js
Normal file
1031
book/common/js/jquery.tablesorter.js
Normal file
File diff suppressed because it is too large
Load diff
219
book/common/js/navhelp.js
Normal file
219
book/common/js/navhelp.js
Normal 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();
|
||||
5
book/common/js/parsons-noconflict.js
Normal file
5
book/common/js/parsons-noconflict.js
Normal 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
61
book/common/js/poll.js
Normal 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
Loading…
Reference in a new issue