-
Notifications
You must be signed in to change notification settings - Fork 1
OpenDSA exercises using latest Khan Academy framework version.
We currently use the Khan Academy exercise framework to drive most of our exercises. What we actually did was to take a snapshot of their infrastructure about two years ago, and make modifications that split us off from their development process. We now need to do something to move forward. The options are to use a new system, adopt KA's new Perseus tool, or reimplement our KA exercise framework integration. Initial investigation of Perseus does not look promising. So far, we can find no collection of exercises associated with Perseus in the way that the main framework contains a large body of exercises. This might mean that Perseus is just a graphical frontend for developing exercises that are stored in the original format.
We decided to have our fork of khan Academy exercises framework so that we can extend it to satisfy OpenDSA requirements. Changes were made with two main points in mind. First, to minimize the impact on OpenDSA existing exercises as much as possible. Second, to keep the new framework core system unchanged as much as we can so that we can continue pulling new features, bug fixes and enhancements from the original repository.
The new framework uses requireJS to load JavaScript modules. During testing we found that loading other libraries like JSAV and CodeMirror using the ordinary way, in a script tag, will not work. We modified the framework to add support to load JSAV, CodeMirror modules using the same mechanism the framework uses to load other JavaScript modules. So whenever exercises needs a module it define it in data-require
attribute in html tag. data-require
attribute accept multiple space delimited values.
Another issue was that the earlier version of Khan Academy has support for summative exercises where exercise developer can mix other exercises into one summary exercise by referring to individual exercises' names. Unfortunately Khan Academy team decided to remove that support, further more they reviewed all their exercises to inline all the remaining summatives without duplication.
We reviewed OpenDSA Exercises folder and found many exercises relaying on this feature for example Sorting Chapter Summary Exercise. So we decided to reimplement this vital feature again, the idea was to load all the exercises (html files referred to in the summary exercise) and construct a multi-part exercise out of it, and hand it to the framework to continue working as normal. Each exercise in the produced muti-part version will preserve its weight as defined in the original summary exercise.
There are 3 simple steps you have to do in order to transform any exercise to work using the new framework:
- If the exercise require JSAV or CodeMirror, you have to add "jsav" or "codemirror" strings in data-require attribute as described earlier.
- Remove all script tags under head element and add the following instead:
<script src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.14/require.min.js"></script>
<script src="../../khan-exercises/local-only/main.js" ></script>
- All JavaScript variable or function written in a script tag under body element should be moved under "vars" element in new "config" variable which is an anonymous function. The moved variables/functions should be defined in a global scope.
Example: Transform BubSortPRO.html to use new framework
Please pay attention to the direction between comments (<-- -->) to realize what was done in order to transform an exercise.
<!--
Bubble Sort mini-proficiency exercise.
Written by Gayathri Subramanian and Cliff Shaffer
-->
<!DOCTYPE html>
<html data-require="math">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bubble Sort Proficiency Exercise</title>
<script src="../../lib/jquery.min.js"></script>
<script src="../../lib/jquery-ui.min.js"></script>
<script src="../../JSAV/lib/jquery.transit.js"></script>
<script src="../../JSAV/lib/raphael.js"></script>
<script src="../../ODSAkhan-exercises/khan-exercise.js"></script>
<script src="../../JSAV/build/JSAV-min.js"></script>
<link rel="stylesheet" href="../../JSAV/css/JSAV.css" type="text/css" />
<link rel="stylesheet" href="../../lib/odsaStyle-min.css" type="text/css" />
</head>
<body data-height="550" data-width="950">
<script>
var
jsav, // The JSAV object
answerArr = [], // The (internal) array that stores the correct answer
cloneArr = [], // A copy of the (internal) array at the start of the exercise for reset
jsavArr, // The array that the user manipulates (JSAV object)
userInput, // Boolean: Tells us if user ever did anything
isSelected, // Boolean: True iff user has already clicked an array element
selected_index; // Position that has been selected by user for swap
// Click event handler on the array
var clickHandler = function (index, e) {
if (selected_index === -1) { // if nothing currently selected
jsavArr.css(index, {"font-size": "130%"});
selected_index = index;
}
else {
jsavArr.swap(selected_index, index);
jsavArr.css(index, {"font-size": "100%"});
jsavArr.css(selected_index, {"font-size": "100%"});
selected_index = -1; // Reset to nothing selected
}
userInput = true;
};
// reset function definition
var f_reset = function () {
jsavArr.clear(); // Re-initialize the displayed array object
jsavArr = jsav.ds.array(cloneArr, {indexed: true, center: false});
jsavArr.click(clickHandler); // Rebind click handler after reset
userInput = false;
selected_index = -1; // Reset to nothing selected
};
// swap two values in array
var swap = function (arr, i, j) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
};
// Initialise the exercise
var initJSAV = function (arr_size) {
var i, j;
userInput = false;
selected_index = -1;
answerArr.length = 0; // Out with the old
// Give random numbers in range 0..999
for (i = 0; i < arr_size; i++) {
answerArr[i] = Math.floor(Math.random() * 1000);
}
// Now make a copy
cloneArr = answerArr.slice(0);
jsav = new JSAV("jsav");
jsav.recorded();
jsavArr = jsav.ds.array(answerArr, {indexed: true, center: false});
// Compute the correct Answer
for (i = 0; i < arr_size - 1; i++) {
if (answerArr[i] > answerArr[i + 1]) {
swap(answerArr, i, i + 1);
}
}
// Bind the clickHandler to handle click events on the array
jsavArr.click(clickHandler);
// Set up handler for reset button
$("#reset").click(function () { f_reset(); });
};
// Check student's answer for correctness: User's array must match answer
var checkAnswer = function (arr_size) {
var i;
for (i = 0; i < arr_size; i++) {
if (jsavArr.value(i) !== answerArr[i]) {
return false;
}
}
return true;
};
</script>
<div class="exercise">
<div class="vars">
<var id="arr_size">randRange(7, 9)</var>
<var id="JSAV">initJSAV(arr_size)</var>
</div>
<div class="problems">
<div> <!-- Supresses answer box -->
<div class="question">
<p>Your task in this exercise is to show the
behavior for one iteration of the outer for loop of Bubble Sort.
Simply click on entries in the array to swap them in the way
that Bubble Sort would during its first pass.</p>
<input id="reset" type="button" value="Reset" />
<div id="jsav" class="jsavcanvas"></div>
</div>
<div class="solution" data-type="custom">
<div class="guess">
[userInput]
</div>
<div class="validator-function">
if (!checkAnswer(arr_size) && !guess[0])
return ""; // User did not click, and correct answer is not
// initial array state
else return checkAnswer(arr_size);
</div>
</div>
<div class="hints">
<p>What does Bubble Sort do?</p>
<p>Move from left to right through the array.</p>
<p>At each position, if the value is greater than the value to
its right, then swap them.</p>
</div>
</div>
</div>
</div>
</body>
</html>
<!--
Bubble Sort mini-proficiency exercise.
Written by Gayathri Subramanian and Cliff Shaffer
-->
<!DOCTYPE html>
<!-- jsav string added to load JSAV module before the exercise is loaded -->
<html data-require="jsav math">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Bubble Sort Proficiency Exercise</title>
<!-- Only require.js and main module are loaded here -->
<script src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.14/require.min.js"></script>
<script src="../../khan-exercises/local-only/main.js" ></script>
</head>
<body data-height="550" data-width="950">
<div class="exercise">
<div class="vars">
<!-- Add new config var and move all javascript code inside it -->
<var id="config">
(function(){ <!-- cerate anonymous function -->
<!-- All variables and funcitons should be defined in global scope -->
window.jsav; // The JSAV object
window.answerArr = []; // The (internal) array that stores the correct answer
window.cloneArr = []; // A copy of the (internal) array at the start of the exercise for reset
window.jsavArr; // The array that the user manipulates (JSAV object)
window.userInput; // Boolean: Tells us if user ever did anything
window.isSelected; // Boolean: True iff user has already clicked an array element
window.selected_index; // Position that has been selected by user for swap
// Click event handler on the array
window.clickHandler = function (index, e) {
if (selected_index === -1) { // if nothing currently selected
jsavArr.css(index, {"font-size": "130%"});
selected_index = index;
}
else {
jsavArr.swap(selected_index, index);
jsavArr.css(index, {"font-size": "100%"});
jsavArr.css(selected_index, {"font-size": "100%"});
selected_index = -1; // Reset to nothing selected
}
userInput = true;
};
// reset function definition
window.f_reset = function () {
jsavArr.clear(); // Re-initialize the displayed array object
jsavArr = jsav.ds.array(cloneArr, {indexed: true, center: false});
jsavArr.click(clickHandler); // Rebind click handler after reset
userInput = false;
selected_index = -1; // Reset to nothing selected
};
// swap two values in array
window.swap = function (arr, i, j) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
};
// Initialise the exercise
window.initJSAV = function (arr_size) {
var i, j;
userInput = false;
selected_index = -1;
answerArr.length = 0; // Out with the old
// Give random numbers in range 0..999
for (i = 0; i < arr_size; i++) {
answerArr[i] = Math.floor(Math.random() * 1000);
}
// Now make a copy
cloneArr = answerArr.slice(0);
jsav = new JSAV("jsav");
jsav.recorded();
jsavArr = jsav.ds.array(answerArr, {indexed: true, center: false});
// Compute the correct Answer
for (i = 0; i < arr_size - 1; i++) {
if (answerArr[i] > answerArr[i + 1]) {
swap(answerArr, i, i + 1);
}
}
// Bind the clickHandler to handle click events on the array
jsavArr.click(clickHandler);
// Set up handler for reset button
$("#reset").click(function () { f_reset(); });
};
// Check student's answer for correctness: User's array must match answer
window.checkAnswer = function (arr_size) {
var i;
for (i = 0; i < arr_size; i++) {
if (jsavArr.value(i) !== answerArr[i]) {
return false;
}
}
return true;
};
}())
</var>
<var id="arr_size">randRange(7, 9)</var>
<var id="JSAV">initJSAV(arr_size)</var>
</div>
<div class="problems">
<!-- This div should contain id attribute, for consistency the value
should be the same as the filename -->
<div id="BubsortPRO">
<div class="question">
<p>Your task in this exercise is to show the
behavior for one iteration of the outer for loop of Bubble Sort.
Simply click on entries in the array to swap them in the way
that Bubble Sort would during its first pass.</p>
<input id="reset" type="button" value="Reset" />
<div id="jsav" class="jsavcanvas"></div>
</div>
<div class="solution" data-type="custom">
<div class="guess">
[userInput]
</div>
<div class="validator-function">
if (!checkAnswer(arr_size) && !guess[0])
return ""; // User did not click, and correct answer is not
// initial array state
else return checkAnswer(arr_size);
</div>
</div>
<div class="hints">
<p>What does Bubble Sort do?</p>
<p>Move from left to right through the array.</p>
<p>At each position, if the value is greater than the value to
its right, then swap them.</p>
</div>
</div>
</div>
</div>
</body>
</html>