Autoformat Problem XBlock Source Files for Consistency (2/2) (#37487)
* fix: run prettier on problem block code * fix: codeql issues
This commit is contained in:
@@ -65,91 +65,103 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
var MersenneTwister = function(seed) {
|
||||
var MersenneTwister = function (seed) {
|
||||
if (seed == undefined) {
|
||||
seed = new Date().getTime();
|
||||
}
|
||||
/* Period parameters */
|
||||
}
|
||||
/* Period parameters */
|
||||
this.N = 624;
|
||||
this.M = 397;
|
||||
this.MATRIX_A = 0x9908b0df; /* constant vector a */
|
||||
this.MATRIX_A = 0x9908b0df; /* constant vector a */
|
||||
this.UPPER_MASK = 0x80000000; /* most significant w-r bits */
|
||||
this.LOWER_MASK = 0x7fffffff; /* least significant r bits */
|
||||
|
||||
|
||||
this.mt = new Array(this.N); /* the array for the state vector */
|
||||
this.mti=this.N+1; /* mti==N+1 means mt[N] is not initialized */
|
||||
this.mti = this.N + 1; /* mti==N+1 means mt[N] is not initialized */
|
||||
|
||||
this.init_genrand(seed);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* initializes mt[N] with a seed */
|
||||
MersenneTwister.prototype.init_genrand = function(s) {
|
||||
MersenneTwister.prototype.init_genrand = function (s) {
|
||||
this.mt[0] = s >>> 0;
|
||||
for (this.mti=1; this.mti<this.N; this.mti++) {
|
||||
var s = this.mt[this.mti-1] ^ (this.mt[this.mti-1] >>> 30);
|
||||
this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
|
||||
+ this.mti;
|
||||
/* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
|
||||
/* In the previous versions, MSBs of the seed affect */
|
||||
/* only MSBs of the array mt[]. */
|
||||
/* 2002/01/09 modified by Makoto Matsumoto */
|
||||
this.mt[this.mti] >>>= 0;
|
||||
/* for >32 bit machines */
|
||||
for (this.mti = 1; this.mti < this.N; this.mti++) {
|
||||
var s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30);
|
||||
this.mt[this.mti] = ((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253 + this.mti;
|
||||
/* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
|
||||
/* In the previous versions, MSBs of the seed affect */
|
||||
/* only MSBs of the array mt[]. */
|
||||
/* 2002/01/09 modified by Makoto Matsumoto */
|
||||
this.mt[this.mti] >>>= 0;
|
||||
/* for >32 bit machines */
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* initialize by an array with array-length */
|
||||
/* init_key is the array for initializing keys */
|
||||
/* key_length is its length */
|
||||
/* slight change for C++, 2004/2/26 */
|
||||
MersenneTwister.prototype.init_by_array = function(init_key, key_length) {
|
||||
MersenneTwister.prototype.init_by_array = function (init_key, key_length) {
|
||||
var i, j, k;
|
||||
this.init_genrand(19650218);
|
||||
i=1; j=0;
|
||||
k = (this.N>key_length ? this.N : key_length);
|
||||
i = 1;
|
||||
j = 0;
|
||||
k = this.N > key_length ? this.N : key_length;
|
||||
for (; k; k--) {
|
||||
var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30)
|
||||
this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + ((s & 0x0000ffff) * 1664525)))
|
||||
+ init_key[j] + j; /* non linear */
|
||||
this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
|
||||
i++; j++;
|
||||
if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; }
|
||||
if (j>=key_length) j=0;
|
||||
}
|
||||
for (k=this.N-1; k; k--) {
|
||||
var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30);
|
||||
this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941))
|
||||
- i; /* non linear */
|
||||
var s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30);
|
||||
this.mt[i] =
|
||||
(this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + (s & 0x0000ffff) * 1664525)) +
|
||||
init_key[j] +
|
||||
j; /* non linear */
|
||||
this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
|
||||
i++;
|
||||
if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; }
|
||||
j++;
|
||||
if (i >= this.N) {
|
||||
this.mt[0] = this.mt[this.N - 1];
|
||||
i = 1;
|
||||
}
|
||||
if (j >= key_length) j = 0;
|
||||
}
|
||||
for (k = this.N - 1; k; k--) {
|
||||
var s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30);
|
||||
this.mt[i] =
|
||||
(this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941)) -
|
||||
i; /* non linear */
|
||||
this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
|
||||
i++;
|
||||
if (i >= this.N) {
|
||||
this.mt[0] = this.mt[this.N - 1];
|
||||
i = 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */
|
||||
}
|
||||
|
||||
this.mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */
|
||||
};
|
||||
|
||||
/* generates a random number on [0,0xffffffff]-interval */
|
||||
MersenneTwister.prototype.genrand_int32 = function() {
|
||||
MersenneTwister.prototype.genrand_int32 = function () {
|
||||
var y;
|
||||
var mag01 = new Array(0x0, this.MATRIX_A);
|
||||
/* mag01[x] = x * MATRIX_A for x=0,1 */
|
||||
|
||||
if (this.mti >= this.N) { /* generate N words at one time */
|
||||
if (this.mti >= this.N) {
|
||||
/* generate N words at one time */
|
||||
var kk;
|
||||
|
||||
if (this.mti == this.N+1) /* if init_genrand() has not been called, */
|
||||
if (this.mti == this.N + 1)
|
||||
/* if init_genrand() has not been called, */
|
||||
this.init_genrand(5489); /* a default initial seed is used */
|
||||
|
||||
for (kk=0;kk<this.N-this.M;kk++) {
|
||||
y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk+1]&this.LOWER_MASK);
|
||||
this.mt[kk] = this.mt[kk+this.M] ^ (y >>> 1) ^ mag01[y & 0x1];
|
||||
for (kk = 0; kk < this.N - this.M; kk++) {
|
||||
y = (this.mt[kk] & this.UPPER_MASK) | (this.mt[kk + 1] & this.LOWER_MASK);
|
||||
this.mt[kk] = this.mt[kk + this.M] ^ (y >>> 1) ^ mag01[y & 0x1];
|
||||
}
|
||||
for (;kk<this.N-1;kk++) {
|
||||
y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk+1]&this.LOWER_MASK);
|
||||
this.mt[kk] = this.mt[kk+(this.M-this.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
|
||||
for (; kk < this.N - 1; kk++) {
|
||||
y = (this.mt[kk] & this.UPPER_MASK) | (this.mt[kk + 1] & this.LOWER_MASK);
|
||||
this.mt[kk] = this.mt[kk + (this.M - this.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
|
||||
}
|
||||
y = (this.mt[this.N-1]&this.UPPER_MASK)|(this.mt[0]&this.LOWER_MASK);
|
||||
this.mt[this.N-1] = this.mt[this.M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
|
||||
y = (this.mt[this.N - 1] & this.UPPER_MASK) | (this.mt[0] & this.LOWER_MASK);
|
||||
this.mt[this.N - 1] = this.mt[this.M - 1] ^ (y >>> 1) ^ mag01[y & 0x1];
|
||||
|
||||
this.mti = 0;
|
||||
}
|
||||
@@ -157,45 +169,46 @@ MersenneTwister.prototype.genrand_int32 = function() {
|
||||
y = this.mt[this.mti++];
|
||||
|
||||
/* Tempering */
|
||||
y ^= (y >>> 11);
|
||||
y ^= y >>> 11;
|
||||
y ^= (y << 7) & 0x9d2c5680;
|
||||
y ^= (y << 15) & 0xefc60000;
|
||||
y ^= (y >>> 18);
|
||||
y ^= y >>> 18;
|
||||
|
||||
return y >>> 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* generates a random number on [0,0x7fffffff]-interval */
|
||||
MersenneTwister.prototype.genrand_int31 = function() {
|
||||
return (this.genrand_int32()>>>1);
|
||||
}
|
||||
|
||||
MersenneTwister.prototype.genrand_int31 = function () {
|
||||
return this.genrand_int32() >>> 1;
|
||||
};
|
||||
|
||||
/* generates a random number on [0,1]-real-interval */
|
||||
MersenneTwister.prototype.genrand_real1 = function() {
|
||||
return this.genrand_int32()*(1.0/4294967295.0);
|
||||
/* divided by 2^32-1 */
|
||||
}
|
||||
MersenneTwister.prototype.genrand_real1 = function () {
|
||||
return this.genrand_int32() * (1.0 / 4294967295.0);
|
||||
/* divided by 2^32-1 */
|
||||
};
|
||||
|
||||
/* generates a random number on [0,1)-real-interval */
|
||||
MersenneTwister.prototype.random = function() {
|
||||
return this.genrand_int32()*(1.0/4294967296.0);
|
||||
MersenneTwister.prototype.random = function () {
|
||||
return this.genrand_int32() * (1.0 / 4294967296.0);
|
||||
/* divided by 2^32 */
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* generates a random number on (0,1)-real-interval */
|
||||
MersenneTwister.prototype.genrand_real3 = function() {
|
||||
return (this.genrand_int32() + 0.5)*(1.0/4294967296.0);
|
||||
MersenneTwister.prototype.genrand_real3 = function () {
|
||||
return (this.genrand_int32() + 0.5) * (1.0 / 4294967296.0);
|
||||
/* divided by 2^32 */
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* generates a random number on [0,1) with 53-bit resolution*/
|
||||
MersenneTwister.prototype.genrand_res53 = function() {
|
||||
var a=this.genrand_int32()>>>5, b=this.genrand_int32()>>>6;
|
||||
return(a*67108864.0+b)*(1.0/9007199254740992.0);
|
||||
}
|
||||
MersenneTwister.prototype.genrand_res53 = function () {
|
||||
var a = this.genrand_int32() >>> 5,
|
||||
b = this.genrand_int32() >>> 6;
|
||||
return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0);
|
||||
};
|
||||
|
||||
/* These real versions are due to Isaku Wada, 2002/01/09 added */
|
||||
if(typeof exports == 'undefined'){
|
||||
if (typeof exports == "undefined") {
|
||||
var root = this;
|
||||
} else {
|
||||
var root = exports;
|
||||
|
||||
@@ -8,14 +8,16 @@
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
class MinimaxProblemDisplay extends XProblemDisplay {
|
||||
|
||||
constructor(state, submission, evaluation, container, submissionField, parameters) {
|
||||
|
||||
{
|
||||
// Hack: trick Babel/TypeScript into allowing this before super.
|
||||
if (false) { super(); }
|
||||
let thisFn = (() => { this; }).toString();
|
||||
let thisName = thisFn.slice(thisFn.indexOf('{') + 1, thisFn.indexOf(';')).trim();
|
||||
if (false) {
|
||||
super();
|
||||
}
|
||||
let thisFn = (() => {
|
||||
this;
|
||||
}).toString();
|
||||
let thisName = thisFn.slice(thisFn.indexOf("{") + 1, thisFn.indexOf(";")).trim();
|
||||
eval(`${thisName} = this;`);
|
||||
}
|
||||
this.state = state;
|
||||
@@ -23,7 +25,9 @@ class MinimaxProblemDisplay extends XProblemDisplay {
|
||||
this.evaluation = evaluation;
|
||||
this.container = container;
|
||||
this.submissionField = submissionField;
|
||||
if (parameters == null) { parameters = {}; }
|
||||
if (parameters == null) {
|
||||
parameters = {};
|
||||
}
|
||||
this.parameters = parameters;
|
||||
super(this.state, this.submission, this.evaluation, this.container, this.submissionField, this.parameters);
|
||||
}
|
||||
@@ -31,7 +35,6 @@ class MinimaxProblemDisplay extends XProblemDisplay {
|
||||
render() {}
|
||||
|
||||
createSubmission() {
|
||||
|
||||
this.newSubmission = {};
|
||||
|
||||
if (this.submission != null) {
|
||||
@@ -39,7 +42,7 @@ class MinimaxProblemDisplay extends XProblemDisplay {
|
||||
const result = [];
|
||||
for (let id in this.submission) {
|
||||
const value = this.submission[id];
|
||||
result.push(this.newSubmission[id] = value);
|
||||
result.push((this.newSubmission[id] = value));
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
@@ -51,5 +54,5 @@ class MinimaxProblemDisplay extends XProblemDisplay {
|
||||
}
|
||||
}
|
||||
|
||||
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
|
||||
const root = typeof exports !== "undefined" && exports !== null ? exports : this;
|
||||
root.TestProblemDisplay = TestProblemDisplay;
|
||||
|
||||
@@ -6,28 +6,31 @@
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
class TestProblemGenerator extends XProblemGenerator {
|
||||
|
||||
constructor(seed, parameters) {
|
||||
|
||||
{
|
||||
// Hack: trick Babel/TypeScript into allowing this before super.
|
||||
if (false) { super(); }
|
||||
let thisFn = (() => { this; }).toString();
|
||||
let thisName = thisFn.slice(thisFn.indexOf('{') + 1, thisFn.indexOf(';')).trim();
|
||||
if (false) {
|
||||
super();
|
||||
}
|
||||
let thisFn = (() => {
|
||||
this;
|
||||
}).toString();
|
||||
let thisName = thisFn.slice(thisFn.indexOf("{") + 1, thisFn.indexOf(";")).trim();
|
||||
eval(`${thisName} = this;`);
|
||||
}
|
||||
if (parameters == null) { parameters = {}; }
|
||||
if (parameters == null) {
|
||||
parameters = {};
|
||||
}
|
||||
this.parameters = parameters;
|
||||
super(seed, this.parameters);
|
||||
}
|
||||
|
||||
generate() {
|
||||
|
||||
this.problemState.value = this.parameters.value;
|
||||
|
||||
return this.problemState;
|
||||
}
|
||||
}
|
||||
|
||||
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
|
||||
const root = typeof exports !== "undefined" && exports !== null ? exports : this;
|
||||
root.generatorClass = TestProblemGenerator;
|
||||
|
||||
@@ -7,31 +7,33 @@
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
class TestProblemGrader extends XProblemGrader {
|
||||
|
||||
constructor(submission, problemState, parameters) {
|
||||
|
||||
{
|
||||
// Hack: trick Babel/TypeScript into allowing this before super.
|
||||
if (false) { super(); }
|
||||
let thisFn = (() => { this; }).toString();
|
||||
let thisName = thisFn.slice(thisFn.indexOf('{') + 1, thisFn.indexOf(';')).trim();
|
||||
if (false) {
|
||||
super();
|
||||
}
|
||||
let thisFn = (() => {
|
||||
this;
|
||||
}).toString();
|
||||
let thisName = thisFn.slice(thisFn.indexOf("{") + 1, thisFn.indexOf(";")).trim();
|
||||
eval(`${thisName} = this;`);
|
||||
}
|
||||
this.submission = submission;
|
||||
this.problemState = problemState;
|
||||
if (parameters == null) { parameters = {}; }
|
||||
if (parameters == null) {
|
||||
parameters = {};
|
||||
}
|
||||
this.parameters = parameters;
|
||||
super(this.submission, this.problemState, this.parameters);
|
||||
}
|
||||
|
||||
solve() {
|
||||
|
||||
return this.solution = {0: this.problemState.value};
|
||||
return (this.solution = { 0: this.problemState.value });
|
||||
}
|
||||
|
||||
grade() {
|
||||
|
||||
if ((this.solution == null)) {
|
||||
if (this.solution == null) {
|
||||
this.solve();
|
||||
}
|
||||
|
||||
@@ -39,7 +41,7 @@ class TestProblemGrader extends XProblemGrader {
|
||||
|
||||
for (let id in this.solution) {
|
||||
const value = this.solution[id];
|
||||
const valueCorrect = (this.submission != null) ? (value === this.submission[id]) : false;
|
||||
const valueCorrect = this.submission != null ? value === this.submission[id] : false;
|
||||
this.evaluation[id] = valueCorrect;
|
||||
if (!valueCorrect) {
|
||||
allCorrect = false;
|
||||
@@ -50,5 +52,5 @@ class TestProblemGrader extends XProblemGrader {
|
||||
}
|
||||
}
|
||||
|
||||
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
|
||||
const root = typeof exports !== "undefined" && exports !== null ? exports : this;
|
||||
root.graderClass = TestProblemGrader;
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
class XProblemGenerator {
|
||||
|
||||
constructor(seed, parameters) {
|
||||
|
||||
if (parameters == null) { parameters = {}; }
|
||||
if (parameters == null) {
|
||||
parameters = {};
|
||||
}
|
||||
this.parameters = parameters;
|
||||
this.random = new MersenneTwister(seed);
|
||||
|
||||
@@ -16,30 +16,28 @@ class XProblemGenerator {
|
||||
}
|
||||
|
||||
generate() {
|
||||
|
||||
console.error("Abstract method called: XProblemGenerator.generate");
|
||||
}
|
||||
}
|
||||
|
||||
class XProblemDisplay {
|
||||
|
||||
constructor(state, submission, evaluation, container, submissionField, parameters) {
|
||||
this.state = state;
|
||||
this.submission = submission;
|
||||
this.evaluation = evaluation;
|
||||
this.container = container;
|
||||
this.submissionField = submissionField;
|
||||
if (parameters == null) { parameters = {}; }
|
||||
if (parameters == null) {
|
||||
parameters = {};
|
||||
}
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
console.error("Abstract method called: XProblemDisplay.render");
|
||||
}
|
||||
|
||||
updateSubmission() {
|
||||
|
||||
this.submissionField.val(JSON.stringify(this.getCurrentSubmission()));
|
||||
}
|
||||
|
||||
@@ -49,30 +47,28 @@ class XProblemDisplay {
|
||||
}
|
||||
|
||||
class XProblemGrader {
|
||||
|
||||
constructor(submission, problemState, parameters) {
|
||||
|
||||
this.submission = submission;
|
||||
this.problemState = problemState;
|
||||
if (parameters == null) { parameters = {}; }
|
||||
if (parameters == null) {
|
||||
parameters = {};
|
||||
}
|
||||
this.parameters = parameters;
|
||||
this.solution = null;
|
||||
this.solution = null;
|
||||
this.evaluation = {};
|
||||
}
|
||||
|
||||
solve() {
|
||||
|
||||
console.error("Abstract method called: XProblemGrader.solve");
|
||||
}
|
||||
|
||||
grade() {
|
||||
|
||||
console.error("Abstract method called: XProblemGrader.grade");
|
||||
}
|
||||
}
|
||||
|
||||
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
|
||||
const root = typeof exports !== "undefined" && exports !== null ? exports : this;
|
||||
|
||||
root.XProblemGenerator = XProblemGenerator;
|
||||
root.XProblemDisplay = XProblemDisplay;
|
||||
root.XProblemGrader = XProblemGrader;
|
||||
root.XProblemDisplay = XProblemDisplay;
|
||||
root.XProblemGrader = XProblemGrader;
|
||||
|
||||
@@ -4,20 +4,20 @@
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
describe('Problem', function() {
|
||||
const problem_content_default = readFixtures('problem_content.html');
|
||||
describe("Problem", function () {
|
||||
const problem_content_default = readFixtures("problem_content.html");
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(function () {
|
||||
// Stub MathJax
|
||||
window.MathJax = {
|
||||
Hub: jasmine.createSpyObj('MathJax.Hub', ['getAllJax', 'Queue']),
|
||||
Callback: jasmine.createSpyObj('MathJax.Callback', ['After'])
|
||||
Hub: jasmine.createSpyObj("MathJax.Hub", ["getAllJax", "Queue"]),
|
||||
Callback: jasmine.createSpyObj("MathJax.Callback", ["After"]),
|
||||
};
|
||||
this.stubbedJax = {root: jasmine.createSpyObj('jax.root', ['toMathML'])};
|
||||
this.stubbedJax = { root: jasmine.createSpyObj("jax.root", ["toMathML"]) };
|
||||
MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]);
|
||||
window.update_schematics = function() {};
|
||||
spyOn(SR, 'readText');
|
||||
spyOn(SR, 'readTexts');
|
||||
window.update_schematics = function () {};
|
||||
spyOn(SR, "readText");
|
||||
spyOn(SR, "readTexts");
|
||||
|
||||
// Load this function from spec/helper.js
|
||||
// Note that if your test fails with a message like:
|
||||
@@ -25,19 +25,18 @@ describe('Problem', function() {
|
||||
// this msg is coming from the stubRequests function else clause.
|
||||
jasmine.stubRequests();
|
||||
|
||||
loadFixtures('problem.html');
|
||||
loadFixtures("problem.html");
|
||||
|
||||
spyOn(Logger, 'log');
|
||||
spyOn($.fn, 'load').and.callFake(function(url, callback) {
|
||||
$(this).html(readFixtures('problem_content.html'));
|
||||
spyOn(Logger, "log");
|
||||
spyOn($.fn, "load").and.callFake(function (url, callback) {
|
||||
$(this).html(readFixtures("problem_content.html"));
|
||||
return callback();
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructor', function() {
|
||||
|
||||
it('set the element from html', function() {
|
||||
this.problem999 = new Problem((`\
|
||||
describe("constructor", function () {
|
||||
it("set the element from html", function () {
|
||||
this.problem999 = new Problem(`\
|
||||
<section class='xblock xblock-student_view xmodule_display xmodule_CapaModule' data-type='Problem'> \
|
||||
<section id='problem_999' \
|
||||
class='problems-wrapper' \
|
||||
@@ -45,383 +44,414 @@ data-problem-id='i4x://edX/999/problem/Quiz' \
|
||||
data-url='/problem/quiz/'> \
|
||||
</section> \
|
||||
</section>\
|
||||
`)
|
||||
);
|
||||
expect(this.problem999.element_id).toBe('problem_999');
|
||||
`);
|
||||
expect(this.problem999.element_id).toBe("problem_999");
|
||||
});
|
||||
|
||||
it('set the element from loadFixtures', function() {
|
||||
this.problem1 = new Problem($('.xblock-student_view'));
|
||||
expect(this.problem1.element_id).toBe('problem_1');
|
||||
it("set the element from loadFixtures", function () {
|
||||
this.problem1 = new Problem($(".xblock-student_view"));
|
||||
expect(this.problem1.element_id).toBe("problem_1");
|
||||
});
|
||||
});
|
||||
|
||||
describe('bind', function() {
|
||||
beforeEach(function() {
|
||||
spyOn(window, 'update_schematics');
|
||||
describe("bind", function () {
|
||||
beforeEach(function () {
|
||||
spyOn(window, "update_schematics");
|
||||
MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]);
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
});
|
||||
|
||||
it('set mathjax typeset', () => expect(MathJax.Hub.Queue).toHaveBeenCalled());
|
||||
it("set mathjax typeset", () => expect(MathJax.Hub.Queue).toHaveBeenCalled());
|
||||
|
||||
it('update schematics', () => expect(window.update_schematics).toHaveBeenCalled());
|
||||
it("update schematics", () => expect(window.update_schematics).toHaveBeenCalled());
|
||||
|
||||
it('bind answer refresh on button click', function() {
|
||||
expect($('div.action button')).toHandleWith('click', this.problem.refreshAnswers);
|
||||
it("bind answer refresh on button click", function () {
|
||||
expect($("div.action button")).toHandleWith("click", this.problem.refreshAnswers);
|
||||
});
|
||||
|
||||
it('bind the submit button', function() {
|
||||
expect($('.action .submit')).toHandleWith('click', this.problem.submit_fd);
|
||||
it("bind the submit button", function () {
|
||||
expect($(".action .submit")).toHandleWith("click", this.problem.submit_fd);
|
||||
});
|
||||
|
||||
it('bind the reset button', function() {
|
||||
expect($('div.action button.reset')).toHandleWith('click', this.problem.reset);
|
||||
it("bind the reset button", function () {
|
||||
expect($("div.action button.reset")).toHandleWith("click", this.problem.reset);
|
||||
});
|
||||
|
||||
it('bind the show button', function() {
|
||||
expect($('.action .show')).toHandleWith('click', this.problem.show);
|
||||
it("bind the show button", function () {
|
||||
expect($(".action .show")).toHandleWith("click", this.problem.show);
|
||||
});
|
||||
|
||||
it('bind the save button', function() {
|
||||
expect($('div.action button.save')).toHandleWith('click', this.problem.save);
|
||||
it("bind the save button", function () {
|
||||
expect($("div.action button.save")).toHandleWith("click", this.problem.save);
|
||||
});
|
||||
|
||||
it('bind the math input', function() {
|
||||
expect($('input.math')).toHandleWith('keyup', this.problem.refreshMath);
|
||||
it("bind the math input", function () {
|
||||
expect($("input.math")).toHandleWith("keyup", this.problem.refreshMath);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bind_with_custom_input_id', function() {
|
||||
beforeEach(function() {
|
||||
spyOn(window, 'update_schematics');
|
||||
describe("bind_with_custom_input_id", function () {
|
||||
beforeEach(function () {
|
||||
spyOn(window, "update_schematics");
|
||||
MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]);
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
return $(this).html(readFixtures('problem_content_1240.html'));
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
return $(this).html(readFixtures("problem_content_1240.html"));
|
||||
});
|
||||
|
||||
it('bind the submit button', function() {
|
||||
expect($('.action .submit')).toHandleWith('click', this.problem.submit_fd);
|
||||
it("bind the submit button", function () {
|
||||
expect($(".action .submit")).toHandleWith("click", this.problem.submit_fd);
|
||||
});
|
||||
|
||||
it('bind the show button', function() {
|
||||
expect($('div.action button.show')).toHandleWith('click', this.problem.show);
|
||||
it("bind the show button", function () {
|
||||
expect($("div.action button.show")).toHandleWith("click", this.problem.show);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('renderProgressState', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
describe("renderProgressState", function () {
|
||||
beforeEach(function () {
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
});
|
||||
|
||||
const testProgessData = function(problem, score, total_possible, attempts, graded, expected_progress_after_render) {
|
||||
problem.el.data('problem-score', score);
|
||||
problem.el.data('problem-total-possible', total_possible);
|
||||
problem.el.data('attempts-used', attempts);
|
||||
problem.el.data('graded', graded);
|
||||
expect(problem.$('.problem-progress').html()).toEqual("");
|
||||
const testProgessData = function (
|
||||
problem,
|
||||
score,
|
||||
total_possible,
|
||||
attempts,
|
||||
graded,
|
||||
expected_progress_after_render,
|
||||
) {
|
||||
problem.el.data("problem-score", score);
|
||||
problem.el.data("problem-total-possible", total_possible);
|
||||
problem.el.data("attempts-used", attempts);
|
||||
problem.el.data("graded", graded);
|
||||
expect(problem.$(".problem-progress").html()).toEqual("");
|
||||
problem.renderProgressState();
|
||||
expect(problem.$('.problem-progress').html()).toEqual(expected_progress_after_render);
|
||||
expect(problem.$(".problem-progress").html()).toEqual(expected_progress_after_render);
|
||||
};
|
||||
|
||||
describe('with a status of "none"', function() {
|
||||
it('reports the number of points possible and graded', function() {
|
||||
describe('with a status of "none"', function () {
|
||||
it("reports the number of points possible and graded", function () {
|
||||
testProgessData(this.problem, 0, 1, 0, "True", "1 point possible (graded)");
|
||||
});
|
||||
|
||||
it('displays the number of points possible when rendering happens with the content', function() {
|
||||
it("displays the number of points possible when rendering happens with the content", function () {
|
||||
testProgessData(this.problem, 0, 2, 0, "True", "2 points possible (graded)");
|
||||
});
|
||||
|
||||
it('reports the number of points possible and ungraded', function() {
|
||||
it("reports the number of points possible and ungraded", function () {
|
||||
testProgessData(this.problem, 0, 1, 0, "False", "1 point possible (ungraded)");
|
||||
});
|
||||
|
||||
it('displays ungraded if number of points possible is 0', function() {
|
||||
it("displays ungraded if number of points possible is 0", function () {
|
||||
testProgessData(this.problem, 0, 0, 0, "False", "0 points possible (ungraded)");
|
||||
});
|
||||
|
||||
it('displays ungraded if number of points possible is 0, even if graded value is True', function() {
|
||||
it("displays ungraded if number of points possible is 0, even if graded value is True", function () {
|
||||
testProgessData(this.problem, 0, 0, 0, "True", "0 points possible (ungraded)");
|
||||
});
|
||||
|
||||
it('reports the correct score with status none and >0 attempts', function() {
|
||||
it("reports the correct score with status none and >0 attempts", function () {
|
||||
testProgessData(this.problem, 0, 1, 1, "True", "0/1 point (graded)");
|
||||
});
|
||||
|
||||
it('reports the correct score with >1 weight, status none, and >0 attempts', function() {
|
||||
it("reports the correct score with >1 weight, status none, and >0 attempts", function () {
|
||||
testProgessData(this.problem, 0, 2, 2, "True", "0/2 points (graded)");
|
||||
});
|
||||
});
|
||||
|
||||
describe('with any other valid status', function() {
|
||||
|
||||
it('reports the current score', function() {
|
||||
describe("with any other valid status", function () {
|
||||
it("reports the current score", function () {
|
||||
testProgessData(this.problem, 1, 1, 1, "True", "1/1 point (graded)");
|
||||
});
|
||||
|
||||
it('shows current score when rendering happens with the content', function() {
|
||||
it("shows current score when rendering happens with the content", function () {
|
||||
testProgessData(this.problem, 2, 2, 1, "True", "2/2 points (graded)");
|
||||
});
|
||||
|
||||
it('reports the current score even if problem is ungraded', function() {
|
||||
it("reports the current score even if problem is ungraded", function () {
|
||||
testProgessData(this.problem, 1, 1, 1, "False", "1/1 point (ungraded)");
|
||||
});
|
||||
});
|
||||
|
||||
describe('with valid status and string containing an integer like "0" for detail', () =>
|
||||
// These tests are to address a failure specific to Chrome 51 and 52 +
|
||||
it('shows 0 points possible for the detail', function() {
|
||||
it("shows 0 points possible for the detail", function () {
|
||||
testProgessData(this.problem, 0, 0, 1, "False", "0 points possible (ungraded)");
|
||||
})
|
||||
);
|
||||
}));
|
||||
|
||||
describe('with a score of null (show_correctness == false)', function() {
|
||||
it('reports the number of points possible and graded, results hidden', function() {
|
||||
describe("with a score of null (show_correctness == false)", function () {
|
||||
it("reports the number of points possible and graded, results hidden", function () {
|
||||
testProgessData(this.problem, null, 1, 0, "True", "1 point possible (graded, results hidden)");
|
||||
});
|
||||
|
||||
it('reports the number of points possible (plural) and graded, results hidden', function() {
|
||||
it("reports the number of points possible (plural) and graded, results hidden", function () {
|
||||
testProgessData(this.problem, null, 2, 0, "True", "2 points possible (graded, results hidden)");
|
||||
});
|
||||
|
||||
it('reports the number of points possible and ungraded, results hidden', function() {
|
||||
it("reports the number of points possible and ungraded, results hidden", function () {
|
||||
testProgessData(this.problem, null, 1, 0, "False", "1 point possible (ungraded, results hidden)");
|
||||
});
|
||||
|
||||
it('displays ungraded if number of points possible is 0, results hidden', function() {
|
||||
it("displays ungraded if number of points possible is 0, results hidden", function () {
|
||||
testProgessData(this.problem, null, 0, 0, "False", "0 points possible (ungraded, results hidden)");
|
||||
});
|
||||
|
||||
it('displays ungraded if number of points possible is 0, even if graded value is True, results hidden', function() {
|
||||
it("displays ungraded if number of points possible is 0, even if graded value is True, results hidden", function () {
|
||||
testProgessData(this.problem, null, 0, 0, "True", "0 points possible (ungraded, results hidden)");
|
||||
});
|
||||
|
||||
it('reports the correct score with status none and >0 attempts, results hidden', function() {
|
||||
it("reports the correct score with status none and >0 attempts, results hidden", function () {
|
||||
testProgessData(this.problem, null, 1, 1, "True", "1 point possible (graded, results hidden)");
|
||||
});
|
||||
|
||||
it('reports the correct score with >1 weight, status none, and >0 attempts, results hidden', function() {
|
||||
it("reports the correct score with >1 weight, status none, and >0 attempts, results hidden", function () {
|
||||
testProgessData(this.problem, null, 2, 2, "True", "2 points possible (graded, results hidden)");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('render', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
describe("render", function () {
|
||||
beforeEach(function () {
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
this.bind = this.problem.bind;
|
||||
spyOn(this.problem, 'bind');
|
||||
spyOn(this.problem, "bind");
|
||||
});
|
||||
|
||||
describe('with content given', function() {
|
||||
beforeEach(function() {
|
||||
this.problem.render('Hello World');
|
||||
describe("with content given", function () {
|
||||
beforeEach(function () {
|
||||
this.problem.render("Hello World");
|
||||
});
|
||||
|
||||
it('render the content', function() {
|
||||
expect(this.problem.el.html()).toEqual('Hello World');
|
||||
it("render the content", function () {
|
||||
expect(this.problem.el.html()).toEqual("Hello World");
|
||||
});
|
||||
|
||||
it('re-bind the content', function() {
|
||||
it("re-bind the content", function () {
|
||||
expect(this.problem.bind).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with no content given', function() {
|
||||
beforeEach(function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({html: "Hello World"}));
|
||||
describe("with no content given", function () {
|
||||
beforeEach(function () {
|
||||
spyOn($, "postWithPrefix").and.callFake((url, callback) => callback({ html: "Hello World" }));
|
||||
this.problem.render();
|
||||
});
|
||||
|
||||
it('load the content via ajax', function() {
|
||||
expect(this.problem.el.html()).toEqual('Hello World');
|
||||
it("load the content via ajax", function () {
|
||||
expect(this.problem.el.html()).toEqual("Hello World");
|
||||
});
|
||||
|
||||
it('re-bind the content', function() {
|
||||
it("re-bind the content", function () {
|
||||
expect(this.problem.bind).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('submit_fd', function() {
|
||||
beforeEach(function() {
|
||||
describe("submit_fd", function () {
|
||||
beforeEach(function () {
|
||||
// Insert an input of type file outside of the problem.
|
||||
$('.xblock-student_view').after('<input type="file" />');
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
spyOn(this.problem, 'submit');
|
||||
$(".xblock-student_view").after('<input type="file" />');
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
spyOn(this.problem, "submit");
|
||||
});
|
||||
|
||||
it('submit method is called if input of type file is not in problem', function() {
|
||||
it("submit method is called if input of type file is not in problem", function () {
|
||||
this.problem.submit_fd();
|
||||
expect(this.problem.submit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('submit', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.problem.answers = 'foo=1&bar=2';
|
||||
describe("submit", function () {
|
||||
beforeEach(function () {
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
this.problem.answers = "foo=1&bar=2";
|
||||
});
|
||||
|
||||
it('log the problem_check event', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
it("log the problem_check event", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
let promise;
|
||||
promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
done(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.submit();
|
||||
expect(Logger.log).toHaveBeenCalledWith('problem_check', 'foo=1&bar=2');
|
||||
expect(Logger.log).toHaveBeenCalledWith("problem_check", "foo=1&bar=2");
|
||||
});
|
||||
|
||||
it('log the problem_graded event, after the problem is done grading.', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
it("log the problem_graded event, after the problem is done grading.", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
let promise;
|
||||
const response = {
|
||||
success: 'correct',
|
||||
contents: 'mock grader response'
|
||||
success: "correct",
|
||||
contents: "mock grader response",
|
||||
};
|
||||
callback(response);
|
||||
promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
done(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.submit();
|
||||
expect(Logger.log).toHaveBeenCalledWith('problem_graded', ['foo=1&bar=2', 'mock grader response'], this.problem.id);
|
||||
expect(Logger.log).toHaveBeenCalledWith(
|
||||
"problem_graded",
|
||||
["foo=1&bar=2", "mock grader response"],
|
||||
this.problem.id,
|
||||
);
|
||||
});
|
||||
|
||||
it('submit the answer for submit', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
it("submit the answer for submit", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
let promise;
|
||||
promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
done(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.submit();
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith('/problem/Problem1/problem_check',
|
||||
'foo=1&bar=2', jasmine.any(Function));
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith(
|
||||
"/problem/Problem1/problem_check",
|
||||
"foo=1&bar=2",
|
||||
jasmine.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
describe('when the response is correct', () =>
|
||||
it('call render with returned content', function() {
|
||||
const contents = '<div class="wrapper-problem-response" aria-label="Question 1"><p>Correct<span class="status">excellent</span></p></div>' +
|
||||
'<div class="wrapper-problem-response" aria-label="Question 2"><p>Yep<span class="status">correct</span></p></div>';
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
describe("when the response is correct", () =>
|
||||
it("call render with returned content", function () {
|
||||
const contents =
|
||||
'<div class="wrapper-problem-response" aria-label="Question 1"><p>Correct<span class="status">excellent</span></p></div>' +
|
||||
'<div class="wrapper-problem-response" aria-label="Question 2"><p>Yep<span class="status">correct</span></p></div>';
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
let promise;
|
||||
callback({success: 'correct', contents});
|
||||
callback({ success: "correct", contents });
|
||||
promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
done(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.submit();
|
||||
expect(this.problem.el).toHaveHtml(contents);
|
||||
expect(window.SR.readTexts).toHaveBeenCalledWith(['Question 1: excellent', 'Question 2: correct']);
|
||||
})
|
||||
);
|
||||
expect(window.SR.readTexts).toHaveBeenCalledWith(["Question 1: excellent", "Question 2: correct"]);
|
||||
}));
|
||||
|
||||
describe('when the response is incorrect', () =>
|
||||
it('call render with returned content', function() {
|
||||
describe("when the response is incorrect", () =>
|
||||
it("call render with returned content", function () {
|
||||
const contents = '<p>Incorrect<span class="status">no, try again</span></p>';
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
let promise;
|
||||
callback({success: 'incorrect', contents});
|
||||
callback({ success: "incorrect", contents });
|
||||
promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
done(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.submit();
|
||||
expect(this.problem.el).toHaveHtml(contents);
|
||||
expect(window.SR.readTexts).toHaveBeenCalledWith(['no, try again']);
|
||||
})
|
||||
);
|
||||
expect(window.SR.readTexts).toHaveBeenCalledWith(["no, try again"]);
|
||||
}));
|
||||
|
||||
it('tests if the submit button is disabled while submitting and the text changes on the button', function() {
|
||||
it("tests if the submit button is disabled while submitting and the text changes on the button", function () {
|
||||
const self = this;
|
||||
const curr_html = this.problem.el.html();
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
// At this point enableButtons should have been called, making the submit button disabled with text 'submitting'
|
||||
let promise;
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submitting');
|
||||
expect(self.problem.submitButton).toHaveAttr("disabled");
|
||||
expect(self.problem.submitButtonLabel.text()).toBe("Submitting");
|
||||
callback({
|
||||
success: 'incorrect', // does not matter if correct or incorrect here
|
||||
contents: curr_html
|
||||
success: "incorrect", // does not matter if correct or incorrect here
|
||||
contents: curr_html,
|
||||
});
|
||||
promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
done(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
// Make sure the submit button is enabled before submitting
|
||||
$('#input_example_1').val('test').trigger('input');
|
||||
expect(this.problem.submitButton).not.toHaveAttr('disabled');
|
||||
$("#input_example_1").val("test").trigger("input");
|
||||
expect(this.problem.submitButton).not.toHaveAttr("disabled");
|
||||
this.problem.submit();
|
||||
// After submit, the button should not be disabled and should have text as 'Submit'
|
||||
expect(this.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
expect(this.problem.submitButton).not.toHaveAttr('disabled');
|
||||
expect(this.problem.submitButtonLabel.text()).toBe("Submit");
|
||||
expect(this.problem.submitButton).not.toHaveAttr("disabled");
|
||||
});
|
||||
});
|
||||
|
||||
describe('submit button on problems', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.submitDisabled = disabled => {
|
||||
describe("submit button on problems", function () {
|
||||
beforeEach(function () {
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
this.submitDisabled = (disabled) => {
|
||||
if (disabled) {
|
||||
expect(this.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(this.problem.submitButton).toHaveAttr("disabled");
|
||||
} else {
|
||||
expect(this.problem.submitButton).not.toHaveAttr('disabled');
|
||||
expect(this.problem.submitButton).not.toHaveAttr("disabled");
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
describe('some basic tests for submit button', () =>
|
||||
it('should become enabled after a value is entered into the text box', function() {
|
||||
$('#input_example_1').val('test').trigger('input');
|
||||
describe("some basic tests for submit button", () =>
|
||||
it("should become enabled after a value is entered into the text box", function () {
|
||||
$("#input_example_1").val("test").trigger("input");
|
||||
this.submitDisabled(false);
|
||||
$('#input_example_1').val('').trigger('input');
|
||||
$("#input_example_1").val("").trigger("input");
|
||||
this.submitDisabled(true);
|
||||
})
|
||||
);
|
||||
}));
|
||||
|
||||
describe('some advanced tests for submit button', function() {
|
||||
const radioButtonProblemHtml = readFixtures('radiobutton_problem.html');
|
||||
const checkboxProblemHtml = readFixtures('checkbox_problem.html');
|
||||
describe("some advanced tests for submit button", function () {
|
||||
const radioButtonProblemHtml = readFixtures("radiobutton_problem.html");
|
||||
const checkboxProblemHtml = readFixtures("checkbox_problem.html");
|
||||
|
||||
it('should become enabled after a checkbox is checked', function() {
|
||||
$('#input_example_1').replaceWith(checkboxProblemHtml);
|
||||
it("should become enabled after a checkbox is checked", function () {
|
||||
$("#input_example_1").replaceWith(checkboxProblemHtml);
|
||||
this.problem.submitAnswersAndSubmitButton(true);
|
||||
this.submitDisabled(true);
|
||||
$('#input_1_1_1').click();
|
||||
$("#input_1_1_1").click();
|
||||
this.submitDisabled(false);
|
||||
$('#input_1_1_1').click();
|
||||
$("#input_1_1_1").click();
|
||||
this.submitDisabled(true);
|
||||
});
|
||||
|
||||
it('should become enabled after a radiobutton is checked', function() {
|
||||
$('#input_example_1').replaceWith(radioButtonProblemHtml);
|
||||
it("should become enabled after a radiobutton is checked", function () {
|
||||
$("#input_example_1").replaceWith(radioButtonProblemHtml);
|
||||
this.problem.submitAnswersAndSubmitButton(true);
|
||||
this.submitDisabled(true);
|
||||
$('#input_1_1_1').attr('checked', true).trigger('click');
|
||||
$("#input_1_1_1").attr("checked", true).trigger("click");
|
||||
this.submitDisabled(false);
|
||||
$('#input_1_1_1').attr('checked', false).trigger('click');
|
||||
$("#input_1_1_1").attr("checked", false).trigger("click");
|
||||
this.submitDisabled(true);
|
||||
});
|
||||
|
||||
it('should become enabled after a value is selected in a selector', function() {
|
||||
it("should become enabled after a value is selected in a selector", function () {
|
||||
const html = `\
|
||||
<div id="problem_sel">
|
||||
<select>
|
||||
@@ -431,199 +461,218 @@ data-url='/problem/quiz/'> \
|
||||
</select>
|
||||
</div>\
|
||||
`;
|
||||
$('#input_example_1').replaceWith(html);
|
||||
$("#input_example_1").replaceWith(html);
|
||||
this.problem.submitAnswersAndSubmitButton(true);
|
||||
this.submitDisabled(true);
|
||||
$("#problem_sel select").val("val2").trigger('change');
|
||||
$("#problem_sel select").val("val2").trigger("change");
|
||||
this.submitDisabled(false);
|
||||
$("#problem_sel select").val("val0").trigger('change');
|
||||
$("#problem_sel select").val("val0").trigger("change");
|
||||
this.submitDisabled(true);
|
||||
});
|
||||
|
||||
it('should become enabled after a radiobutton is checked and a value is entered into the text box', function() {
|
||||
$(radioButtonProblemHtml).insertAfter('#input_example_1');
|
||||
it("should become enabled after a radiobutton is checked and a value is entered into the text box", function () {
|
||||
$(radioButtonProblemHtml).insertAfter("#input_example_1");
|
||||
this.problem.submitAnswersAndSubmitButton(true);
|
||||
this.submitDisabled(true);
|
||||
$('#input_1_1_1').attr('checked', true).trigger('click');
|
||||
$("#input_1_1_1").attr("checked", true).trigger("click");
|
||||
this.submitDisabled(true);
|
||||
$('#input_example_1').val('111').trigger('input');
|
||||
$("#input_example_1").val("111").trigger("input");
|
||||
this.submitDisabled(false);
|
||||
$('#input_1_1_1').attr('checked', false).trigger('click');
|
||||
$("#input_1_1_1").attr("checked", false).trigger("click");
|
||||
this.submitDisabled(true);
|
||||
});
|
||||
|
||||
it('should become enabled if there are only hidden input fields', function() {
|
||||
it("should become enabled if there are only hidden input fields", function () {
|
||||
const html = `\
|
||||
<input type="text" name="test" id="test" aria-describedby="answer_test" value="" style="display:none;">\
|
||||
`;
|
||||
$('#input_example_1').replaceWith(html);
|
||||
$("#input_example_1").replaceWith(html);
|
||||
this.problem.submitAnswersAndSubmitButton(true);
|
||||
this.submitDisabled(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
describe("reset", function () {
|
||||
beforeEach(function () {
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
});
|
||||
|
||||
it('log the problem_reset event', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
it("log the problem_reset event", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
let promise;
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
promise = {
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.answers = 'foo=1&bar=2';
|
||||
this.problem.answers = "foo=1&bar=2";
|
||||
this.problem.reset();
|
||||
expect(Logger.log).toHaveBeenCalledWith('problem_reset', 'foo=1&bar=2');
|
||||
expect(Logger.log).toHaveBeenCalledWith("problem_reset", "foo=1&bar=2");
|
||||
});
|
||||
|
||||
it('POST to the problem reset page', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
it("POST to the problem reset page", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
let promise;
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
promise = {
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.reset();
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith('/problem/Problem1/problem_reset',
|
||||
{ id: 'i4x://edX/101/problem/Problem1' }, jasmine.any(Function));
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith(
|
||||
"/problem/Problem1/problem_reset",
|
||||
{ id: "i4x://edX/101/problem/Problem1" },
|
||||
jasmine.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('render the returned content', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
it("render the returned content", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
let promise;
|
||||
callback({html: "Reset", success: true});
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
callback({ html: "Reset", success: true });
|
||||
promise = {
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.reset();
|
||||
expect(this.problem.el.html()).toEqual('Reset');
|
||||
expect(this.problem.el.html()).toEqual("Reset");
|
||||
});
|
||||
|
||||
it('sends a message to the window SR element', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
it("sends a message to the window SR element", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
let promise;
|
||||
callback({html: "Reset", success: true});
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
callback({ html: "Reset", success: true });
|
||||
promise = {
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.reset();
|
||||
expect(window.SR.readText).toHaveBeenCalledWith('This problem has been reset.');
|
||||
expect(window.SR.readText).toHaveBeenCalledWith("This problem has been reset.");
|
||||
});
|
||||
|
||||
it('shows a notification on error', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
it("shows a notification on error", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
let promise;
|
||||
callback({msg: "Error on reset.", success: false});
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
callback({ msg: "Error on reset.", success: false });
|
||||
promise = {
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.reset();
|
||||
expect($('.notification-gentle-alert .notification-message').text()).toEqual("Error on reset.");
|
||||
expect($(".notification-gentle-alert .notification-message").text()).toEqual("Error on reset.");
|
||||
});
|
||||
|
||||
it('tests that reset does not enable submit or modify the text while resetting', function() {
|
||||
it("tests that reset does not enable submit or modify the text while resetting", function () {
|
||||
const self = this;
|
||||
const curr_html = this.problem.el.html();
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
// enableButtons should have been called at this point to set them to all disabled
|
||||
let promise;
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
callback({success: 'correct', html: curr_html});
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
expect(self.problem.submitButton).toHaveAttr("disabled");
|
||||
expect(self.problem.submitButtonLabel.text()).toBe("Submit");
|
||||
callback({ success: "correct", html: curr_html });
|
||||
promise = {
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
// Submit should be disabled
|
||||
expect(this.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(this.problem.submitButton).toHaveAttr("disabled");
|
||||
this.problem.reset();
|
||||
// Submit should remain disabled
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
expect(self.problem.submitButton).toHaveAttr("disabled");
|
||||
expect(self.problem.submitButtonLabel.text()).toBe("Submit");
|
||||
});
|
||||
});
|
||||
|
||||
describe('show problem with column in id', function() {
|
||||
describe("show problem with column in id", function () {
|
||||
beforeEach(function () {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
this.problem.el.prepend('<div id="answer_1_1:11" /><div id="answer_1_2:12" />');
|
||||
});
|
||||
|
||||
it('log the problem_show event', function() {
|
||||
it("log the problem_show event", function () {
|
||||
this.problem.show();
|
||||
expect(Logger.log).toHaveBeenCalledWith('problem_show',
|
||||
{problem: 'i4x://edX/101/problem/Problem1'});
|
||||
expect(Logger.log).toHaveBeenCalledWith("problem_show", { problem: "i4x://edX/101/problem/Problem1" });
|
||||
});
|
||||
|
||||
it('fetch the answers', function() {
|
||||
spyOn($, 'postWithPrefix');
|
||||
it("fetch the answers", function () {
|
||||
spyOn($, "postWithPrefix");
|
||||
this.problem.show();
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith('/problem/Problem1/problem_show',
|
||||
jasmine.any(Function));
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith("/problem/Problem1/problem_show", jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('show the answers', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(
|
||||
(url, callback) => callback({answers: {'1_1:11': 'One', '1_2:12': 'Two'}})
|
||||
it("show the answers", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake((url, callback) =>
|
||||
callback({ answers: { "1_1:11": "One", "1_2:12": "Two" } }),
|
||||
);
|
||||
this.problem.show();
|
||||
expect($("#answer_1_1\\:11")).toHaveHtml('One');
|
||||
expect($("#answer_1_2\\:12")).toHaveHtml('Two');
|
||||
expect($("#answer_1_1\\:11")).toHaveHtml("One");
|
||||
expect($("#answer_1_2\\:12")).toHaveHtml("Two");
|
||||
});
|
||||
|
||||
it('disables the show answer button', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({answers: {}}));
|
||||
it("disables the show answer button", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake((url, callback) => callback({ answers: {} }));
|
||||
this.problem.show();
|
||||
expect(this.problem.el.find('.show').attr('disabled')).toEqual('disabled');
|
||||
expect(this.problem.el.find(".show").attr("disabled")).toEqual("disabled");
|
||||
});
|
||||
});
|
||||
|
||||
describe('show', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
describe("show", function () {
|
||||
beforeEach(function () {
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
this.problem.el.prepend('<div id="answer_1_1" /><div id="answer_1_2" />');
|
||||
});
|
||||
|
||||
describe('when the answer has not yet shown', function() {
|
||||
beforeEach(function() {
|
||||
expect(this.problem.el.find('.show').attr('disabled')).not.toEqual('disabled');
|
||||
describe("when the answer has not yet shown", function () {
|
||||
beforeEach(function () {
|
||||
expect(this.problem.el.find(".show").attr("disabled")).not.toEqual("disabled");
|
||||
});
|
||||
|
||||
it('log the problem_show event', function() {
|
||||
it("log the problem_show event", function () {
|
||||
this.problem.show();
|
||||
expect(Logger.log).toHaveBeenCalledWith('problem_show',
|
||||
{problem: 'i4x://edX/101/problem/Problem1'});
|
||||
expect(Logger.log).toHaveBeenCalledWith("problem_show", { problem: "i4x://edX/101/problem/Problem1" });
|
||||
});
|
||||
|
||||
it('fetch the answers', function() {
|
||||
spyOn($, 'postWithPrefix');
|
||||
it("fetch the answers", function () {
|
||||
spyOn($, "postWithPrefix");
|
||||
this.problem.show();
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith('/problem/Problem1/problem_show',
|
||||
jasmine.any(Function));
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith("/problem/Problem1/problem_show", jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('show the answers', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({answers: {'1_1': 'One', '1_2': 'Two'}}));
|
||||
it("show the answers", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake((url, callback) =>
|
||||
callback({ answers: { "1_1": "One", "1_2": "Two" } }),
|
||||
);
|
||||
this.problem.show();
|
||||
expect($('#answer_1_1')).toHaveHtml('One');
|
||||
expect($('#answer_1_2')).toHaveHtml('Two');
|
||||
expect($("#answer_1_1")).toHaveHtml("One");
|
||||
expect($("#answer_1_2")).toHaveHtml("Two");
|
||||
});
|
||||
|
||||
it('disables the show answer button', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({answers: {}}));
|
||||
it("disables the show answer button", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake((url, callback) => callback({ answers: {} }));
|
||||
this.problem.show();
|
||||
expect(this.problem.el.find('.show').attr('disabled')).toEqual('disabled');
|
||||
expect(this.problem.el.find(".show").attr("disabled")).toEqual("disabled");
|
||||
});
|
||||
|
||||
describe('radio text question', function() {
|
||||
const radio_text_xml=`\
|
||||
describe("radio text question", function () {
|
||||
const radio_text_xml = `\
|
||||
<section class="problem">
|
||||
<div><p></p><span><section id="choicetextinput_1_2_1" class="choicetextinput">
|
||||
|
||||
@@ -650,44 +699,47 @@ data-url='/problem/quiz/'> \
|
||||
</section></span></div>
|
||||
</section>\
|
||||
`;
|
||||
beforeEach(function() {
|
||||
beforeEach(function () {
|
||||
// Append a radiotextresponse problem to the problem, so we can check it's javascript functionality
|
||||
this.problem.el.prepend(radio_text_xml);
|
||||
});
|
||||
|
||||
it('sets the correct class on the section for the correct choice', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({answers: {"1_2_1": ["1_2_1_choiceinput_0bc"], "1_2_1_choiceinput_0bc": "3"}}));
|
||||
it("sets the correct class on the section for the correct choice", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake((url, callback) =>
|
||||
callback({ answers: { "1_2_1": ["1_2_1_choiceinput_0bc"], "1_2_1_choiceinput_0bc": "3" } }),
|
||||
);
|
||||
this.problem.show();
|
||||
|
||||
expect($('#forinput1_2_1_choiceinput_0bc').attr('class')).toEqual(
|
||||
'choicetextgroup_show_correct');
|
||||
expect($('#answer_1_2_1_choiceinput_0bc').text()).toEqual('3');
|
||||
expect($('#answer_1_2_1_choiceinput_1bc').text()).toEqual('');
|
||||
expect($('#answer_1_2_1_choiceinput_2bc').text()).toEqual('');
|
||||
expect($("#forinput1_2_1_choiceinput_0bc").attr("class")).toEqual("choicetextgroup_show_correct");
|
||||
expect($("#answer_1_2_1_choiceinput_0bc").text()).toEqual("3");
|
||||
expect($("#answer_1_2_1_choiceinput_1bc").text()).toEqual("");
|
||||
expect($("#answer_1_2_1_choiceinput_2bc").text()).toEqual("");
|
||||
});
|
||||
|
||||
it('Should not disable input fields', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({answers: {"1_2_1": ["1_2_1_choiceinput_0bc"], "1_2_1_choiceinput_0bc": "3"}}));
|
||||
it("Should not disable input fields", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake((url, callback) =>
|
||||
callback({ answers: { "1_2_1": ["1_2_1_choiceinput_0bc"], "1_2_1_choiceinput_0bc": "3" } }),
|
||||
);
|
||||
this.problem.show();
|
||||
expect($('input#1_2_1_choiceinput_0bc').attr('disabled')).not.toEqual('disabled');
|
||||
expect($('input#1_2_1_choiceinput_1bc').attr('disabled')).not.toEqual('disabled');
|
||||
expect($('input#1_2_1_choiceinput_2bc').attr('disabled')).not.toEqual('disabled');
|
||||
expect($('input#1_2_1').attr('disabled')).not.toEqual('disabled');
|
||||
expect($("input#1_2_1_choiceinput_0bc").attr("disabled")).not.toEqual("disabled");
|
||||
expect($("input#1_2_1_choiceinput_1bc").attr("disabled")).not.toEqual("disabled");
|
||||
expect($("input#1_2_1_choiceinput_2bc").attr("disabled")).not.toEqual("disabled");
|
||||
expect($("input#1_2_1").attr("disabled")).not.toEqual("disabled");
|
||||
});
|
||||
});
|
||||
|
||||
describe('imageinput', function() {
|
||||
describe("imageinput", function () {
|
||||
let el, height, width;
|
||||
const imageinput_html = readFixtures('imageinput.underscore');
|
||||
const imageinput_html = readFixtures("imageinput.underscore");
|
||||
|
||||
const DEFAULTS = {
|
||||
id: '12345',
|
||||
width: '300',
|
||||
height: '400'
|
||||
id: "12345",
|
||||
width: "300",
|
||||
height: "400",
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
beforeEach(function () {
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
this.problem.el.prepend(_.template(imageinput_html)(DEFAULTS));
|
||||
});
|
||||
|
||||
@@ -695,23 +747,23 @@ data-url='/problem/quiz/'> \
|
||||
stubRequest(data);
|
||||
problem.show();
|
||||
|
||||
$.each(data['answers'], (id, answer) => {
|
||||
$.each(data["answers"], (id, answer) => {
|
||||
const img = getImage(answer);
|
||||
el = $(`#inputtype_${id}`);
|
||||
expect(img).toImageDiffEqual(el.find('canvas')[0]);
|
||||
expect(img).toImageDiffEqual(el.find("canvas")[0]);
|
||||
});
|
||||
};
|
||||
|
||||
var stubRequest = data => {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback(data));
|
||||
var stubRequest = (data) => {
|
||||
spyOn($, "postWithPrefix").and.callFake((url, callback) => callback(data));
|
||||
};
|
||||
|
||||
var getImage = (coords, c_width, c_height) => {
|
||||
let ctx, reg;
|
||||
const types = {
|
||||
rectangle: coords => {
|
||||
rectangle: (coords) => {
|
||||
reg = /^\(([0-9]+),([0-9]+)\)-\(([0-9]+),([0-9]+)\)$/;
|
||||
const rects = coords.replace(/\s*/g, '').split(/;/);
|
||||
const rects = coords.replace(/\s*/g, "").split(/;/);
|
||||
|
||||
$.each(rects, (index, rect) => {
|
||||
const { abs } = Math;
|
||||
@@ -728,8 +780,8 @@ data-url='/problem/quiz/'> \
|
||||
ctx.fill();
|
||||
},
|
||||
|
||||
regions: coords => {
|
||||
const parseCoords = coords => {
|
||||
regions: (coords) => {
|
||||
const parseCoords = (coords) => {
|
||||
reg = JSON.parse(coords);
|
||||
|
||||
if (typeof reg[0][0][0] === "undefined") {
|
||||
@@ -753,276 +805,295 @@ data-url='/problem/quiz/'> \
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = c_width || 100;
|
||||
canvas.height = c_height || 100;
|
||||
|
||||
if (canvas.getContext) {
|
||||
ctx = canvas.getContext('2d');
|
||||
ctx = canvas.getContext("2d");
|
||||
} else {
|
||||
console.log('Canvas is not supported.');
|
||||
console.log("Canvas is not supported.");
|
||||
}
|
||||
|
||||
ctx.fillStyle = 'rgba(255,255,255,.3)';
|
||||
ctx.fillStyle = "rgba(255,255,255,.3)";
|
||||
ctx.strokeStyle = "#FF0000";
|
||||
ctx.lineWidth = "2";
|
||||
|
||||
$.each(coords, (key, value) => {
|
||||
if ((types[key] != null) && value) { return types[key](value); }
|
||||
if (types[key] != null && value) {
|
||||
return types[key](value);
|
||||
}
|
||||
});
|
||||
|
||||
return canvas;
|
||||
};
|
||||
|
||||
it('rectangle is drawn correctly', function() {
|
||||
it("rectangle is drawn correctly", function () {
|
||||
assertAnswer(this.problem, {
|
||||
'answers': {
|
||||
'12345': {
|
||||
'rectangle': '(10,10)-(30,30)',
|
||||
'regions': null
|
||||
}
|
||||
}
|
||||
answers: {
|
||||
12345: {
|
||||
rectangle: "(10,10)-(30,30)",
|
||||
regions: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('region is drawn correctly', function() {
|
||||
it("region is drawn correctly", function () {
|
||||
assertAnswer(this.problem, {
|
||||
'answers': {
|
||||
'12345': {
|
||||
'rectangle': null,
|
||||
'regions': '[[10,10],[30,30],[70,30],[20,30]]'
|
||||
}
|
||||
}
|
||||
answers: {
|
||||
12345: {
|
||||
rectangle: null,
|
||||
regions: "[[10,10],[30,30],[70,30],[20,30]]",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('mixed shapes are drawn correctly', function() {
|
||||
it("mixed shapes are drawn correctly", function () {
|
||||
assertAnswer(this.problem, {
|
||||
'answers': {'12345': {
|
||||
'rectangle': '(10,10)-(30,30);(5,5)-(20,20)',
|
||||
'regions': `[
|
||||
answers: {
|
||||
12345: {
|
||||
rectangle: "(10,10)-(30,30);(5,5)-(20,20)",
|
||||
regions: `[
|
||||
[[50,50],[40,40],[70,30],[50,70]],
|
||||
[[90,95],[95,95],[90,70],[70,70]]
|
||||
]`
|
||||
}
|
||||
}
|
||||
]`,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('multiple image inputs draw answers on separate canvases', function() {
|
||||
it("multiple image inputs draw answers on separate canvases", function () {
|
||||
const data = {
|
||||
id: '67890',
|
||||
width: '400',
|
||||
height: '300'
|
||||
id: "67890",
|
||||
width: "400",
|
||||
height: "300",
|
||||
};
|
||||
|
||||
this.problem.el.prepend(_.template(imageinput_html)(data));
|
||||
assertAnswer(this.problem, {
|
||||
'answers': {
|
||||
'12345': {
|
||||
'rectangle': null,
|
||||
'regions': '[[10,10],[30,30],[70,30],[20,30]]'
|
||||
answers: {
|
||||
12345: {
|
||||
rectangle: null,
|
||||
regions: "[[10,10],[30,30],[70,30],[20,30]]",
|
||||
},
|
||||
'67890': {
|
||||
'rectangle': '(10,10)-(30,30)',
|
||||
'regions': null
|
||||
}
|
||||
}
|
||||
67890: {
|
||||
rectangle: "(10,10)-(30,30)",
|
||||
regions: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('dictionary with answers doesn\'t contain answer for current id', function() {
|
||||
spyOn(console, 'log');
|
||||
stubRequest({'answers':{}});
|
||||
it("dictionary with answers doesn't contain answer for current id", function () {
|
||||
spyOn(console, "log");
|
||||
stubRequest({ answers: {} });
|
||||
this.problem.show();
|
||||
el = $('#inputtype_12345');
|
||||
expect(el.find('canvas')).not.toExist();
|
||||
expect(console.log).toHaveBeenCalledWith('Answer is absent for image input with id=12345');
|
||||
el = $("#inputtype_12345");
|
||||
expect(el.find("canvas")).not.toExist();
|
||||
expect(console.log).toHaveBeenCalledWith("Answer is absent for image input with id=12345");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('save', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.problem.answers = 'foo=1&bar=2';
|
||||
describe("save", function () {
|
||||
beforeEach(function () {
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
this.problem.answers = "foo=1&bar=2";
|
||||
});
|
||||
|
||||
it('log the problem_save event', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
it("log the problem_save event", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
let promise;
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
promise = {
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.save();
|
||||
expect(Logger.log).toHaveBeenCalledWith('problem_save', 'foo=1&bar=2');
|
||||
expect(Logger.log).toHaveBeenCalledWith("problem_save", "foo=1&bar=2");
|
||||
});
|
||||
|
||||
it('POST to save problem', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
it("POST to save problem", function () {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
let promise;
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
promise = {
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.save();
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith('/problem/Problem1/problem_save',
|
||||
'foo=1&bar=2', jasmine.any(Function));
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith(
|
||||
"/problem/Problem1/problem_save",
|
||||
"foo=1&bar=2",
|
||||
jasmine.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('tests that save does not enable the submit button or change the text when submit is originally disabled', function() {
|
||||
it("tests that save does not enable the submit button or change the text when submit is originally disabled", function () {
|
||||
const self = this;
|
||||
const curr_html = this.problem.el.html();
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
// enableButtons should have been called at this point and the submit button should be unaffected
|
||||
let promise;
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
callback({success: 'correct', html: curr_html});
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
expect(self.problem.submitButton).toHaveAttr("disabled");
|
||||
expect(self.problem.submitButtonLabel.text()).toBe("Submit");
|
||||
callback({ success: "correct", html: curr_html });
|
||||
promise = {
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
// Expect submit to be disabled and labeled properly at the start
|
||||
expect(this.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(this.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
expect(this.problem.submitButton).toHaveAttr("disabled");
|
||||
expect(this.problem.submitButtonLabel.text()).toBe("Submit");
|
||||
this.problem.save();
|
||||
// Submit button should have the same state after save has completed
|
||||
expect(this.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(this.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
expect(this.problem.submitButton).toHaveAttr("disabled");
|
||||
expect(this.problem.submitButtonLabel.text()).toBe("Submit");
|
||||
});
|
||||
|
||||
it('tests that save does not disable the submit button or change the text when submit is originally enabled', function() {
|
||||
it("tests that save does not disable the submit button or change the text when submit is originally enabled", function () {
|
||||
const self = this;
|
||||
const curr_html = this.problem.el.html();
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
spyOn($, "postWithPrefix").and.callFake(function (url, answers, callback) {
|
||||
// enableButtons should have been called at this point, and the submit button should be disabled while submitting
|
||||
let promise;
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
callback({success: 'correct', html: curr_html});
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
expect(self.problem.submitButton).toHaveAttr("disabled");
|
||||
expect(self.problem.submitButtonLabel.text()).toBe("Submit");
|
||||
callback({ success: "correct", html: curr_html });
|
||||
promise = {
|
||||
always(callable) {
|
||||
return callable();
|
||||
},
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
// Expect submit to be enabled and labeled properly at the start after adding an input
|
||||
$('#input_example_1').val('test').trigger('input');
|
||||
expect(this.problem.submitButton).not.toHaveAttr('disabled');
|
||||
expect(this.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
$("#input_example_1").val("test").trigger("input");
|
||||
expect(this.problem.submitButton).not.toHaveAttr("disabled");
|
||||
expect(this.problem.submitButtonLabel.text()).toBe("Submit");
|
||||
this.problem.save();
|
||||
// Submit button should have the same state after save has completed
|
||||
expect(this.problem.submitButton).not.toHaveAttr('disabled');
|
||||
expect(this.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
expect(this.problem.submitButton).not.toHaveAttr("disabled");
|
||||
expect(this.problem.submitButtonLabel.text()).toBe("Submit");
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshMath', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
$('#input_example_1').val('E=mc^2');
|
||||
this.problem.refreshMath({target: $('#input_example_1').get(0)});
|
||||
describe("refreshMath", function () {
|
||||
beforeEach(function () {
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
$("#input_example_1").val("E=mc^2");
|
||||
this.problem.refreshMath({ target: $("#input_example_1").get(0) });
|
||||
});
|
||||
|
||||
it('should queue the conversion and MathML element update', function() {
|
||||
expect(MathJax.Hub.Queue).toHaveBeenCalledWith(['Text', this.stubbedJax, 'E=mc^2'],
|
||||
[this.problem.updateMathML, this.stubbedJax, $('#input_example_1').get(0)]);
|
||||
it("should queue the conversion and MathML element update", function () {
|
||||
expect(MathJax.Hub.Queue).toHaveBeenCalledWith(
|
||||
["Text", this.stubbedJax, "E=mc^2"],
|
||||
[this.problem.updateMathML, this.stubbedJax, $("#input_example_1").get(0)],
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateMathML', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.stubbedJax.root.toMathML.and.returnValue('<MathML>');
|
||||
describe("updateMathML", function () {
|
||||
beforeEach(function () {
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
this.stubbedJax.root.toMathML.and.returnValue("<MathML>");
|
||||
});
|
||||
|
||||
describe('when there is no exception', function() {
|
||||
beforeEach(function() {
|
||||
this.problem.updateMathML(this.stubbedJax, $('#input_example_1').get(0));
|
||||
describe("when there is no exception", function () {
|
||||
beforeEach(function () {
|
||||
this.problem.updateMathML(this.stubbedJax, $("#input_example_1").get(0));
|
||||
});
|
||||
|
||||
it('convert jax to MathML', () => expect($('#input_example_1_dynamath')).toHaveValue('<MathML>'));
|
||||
it("convert jax to MathML", () => expect($("#input_example_1_dynamath")).toHaveValue("<MathML>"));
|
||||
});
|
||||
|
||||
describe('when there is an exception', function() {
|
||||
beforeEach(function() {
|
||||
describe("when there is an exception", function () {
|
||||
beforeEach(function () {
|
||||
const error = new Error();
|
||||
error.restart = true;
|
||||
this.stubbedJax.root.toMathML.and.throwError(error);
|
||||
this.problem.updateMathML(this.stubbedJax, $('#input_example_1').get(0));
|
||||
this.problem.updateMathML(this.stubbedJax, $("#input_example_1").get(0));
|
||||
});
|
||||
|
||||
it('should queue up the exception', function() {
|
||||
it("should queue up the exception", function () {
|
||||
expect(MathJax.Callback.After).toHaveBeenCalledWith([this.problem.refreshMath, this.stubbedJax], true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshAnswers', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
describe("refreshAnswers", function () {
|
||||
beforeEach(function () {
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
this.problem.el.html(`\
|
||||
<textarea class="CodeMirror" />
|
||||
<input id="input_1_1" name="input_1_1" class="schematic" value="one" />
|
||||
<input id="input_1_2" name="input_1_2" value="two" />
|
||||
<input id="input_bogus_3" name="input_bogus_3" value="three" />\
|
||||
`
|
||||
);
|
||||
this.stubSchematic = { update_value: jasmine.createSpy('schematic') };
|
||||
this.stubCodeMirror = { save: jasmine.createSpy('CodeMirror') };
|
||||
$('input.schematic').get(0).schematic = this.stubSchematic;
|
||||
$('textarea.CodeMirror').get(0).CodeMirror = this.stubCodeMirror;
|
||||
`);
|
||||
this.stubSchematic = { update_value: jasmine.createSpy("schematic") };
|
||||
this.stubCodeMirror = { save: jasmine.createSpy("CodeMirror") };
|
||||
$("input.schematic").get(0).schematic = this.stubSchematic;
|
||||
$("textarea.CodeMirror").get(0).CodeMirror = this.stubCodeMirror;
|
||||
});
|
||||
|
||||
it('update each schematic', function() {
|
||||
it("update each schematic", function () {
|
||||
this.problem.refreshAnswers();
|
||||
expect(this.stubSchematic.update_value).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('update each code block', function() {
|
||||
it("update each code block", function () {
|
||||
this.problem.refreshAnswers();
|
||||
expect(this.stubCodeMirror.save).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple JsInput in single problem', function() {
|
||||
const jsinput_html = readFixtures('jsinput_problem.html');
|
||||
describe("multiple JsInput in single problem", function () {
|
||||
const jsinput_html = readFixtures("jsinput_problem.html");
|
||||
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
beforeEach(function () {
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
this.problem.render(jsinput_html);
|
||||
});
|
||||
|
||||
it('submit_save_waitfor should return false', function() {
|
||||
$(this.problem.inputs[0]).data('waitfor', function() {});
|
||||
it("submit_save_waitfor should return false", function () {
|
||||
$(this.problem.inputs[0]).data("waitfor", function () {});
|
||||
expect(this.problem.submit_save_waitfor()).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Submitting an xqueue-graded problem', function() {
|
||||
const matlabinput_html = readFixtures('matlabinput_problem.html');
|
||||
describe("Submitting an xqueue-graded problem", function () {
|
||||
const matlabinput_html = readFixtures("matlabinput_problem.html");
|
||||
|
||||
beforeEach(function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({html: matlabinput_html}));
|
||||
beforeEach(function () {
|
||||
spyOn($, "postWithPrefix").and.callFake((url, callback) => callback({ html: matlabinput_html }));
|
||||
jasmine.clock().install();
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
spyOn(this.problem, 'poll').and.callThrough();
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
spyOn(this.problem, "poll").and.callThrough();
|
||||
this.problem.render(matlabinput_html);
|
||||
});
|
||||
|
||||
afterEach(() => jasmine.clock().uninstall());
|
||||
|
||||
it('check that we stop polling after a fixed amount of time', function() {
|
||||
it("check that we stop polling after a fixed amount of time", function () {
|
||||
expect(this.problem.poll).not.toHaveBeenCalled();
|
||||
jasmine.clock().tick(1);
|
||||
const time_steps = [1000, 2000, 4000, 8000, 16000, 32000];
|
||||
let num_calls = 1;
|
||||
for (let time_step of Array.from(time_steps)) {
|
||||
(time_step => {
|
||||
((time_step) => {
|
||||
jasmine.clock().tick(time_step);
|
||||
expect(this.problem.poll.calls.count()).toEqual(num_calls);
|
||||
num_calls += 1;
|
||||
@@ -1033,72 +1104,72 @@ data-url='/problem/quiz/'> \
|
||||
jasmine.clock().tick(64000);
|
||||
expect(this.problem.poll.calls.count()).toEqual(6);
|
||||
|
||||
expect($('.notification-gentle-alert .notification-message').text()).toEqual("The grading process is still running. Refresh the page to see updates.");
|
||||
expect($(".notification-gentle-alert .notification-message").text()).toEqual(
|
||||
"The grading process is still running. Refresh the page to see updates.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('codeinput problem', function() {
|
||||
const codeinputProblemHtml = readFixtures('codeinput_problem.html');
|
||||
describe("codeinput problem", function () {
|
||||
const codeinputProblemHtml = readFixtures("codeinput_problem.html");
|
||||
|
||||
beforeEach(function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({html: codeinputProblemHtml}));
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
beforeEach(function () {
|
||||
spyOn($, "postWithPrefix").and.callFake((url, callback) => callback({ html: codeinputProblemHtml }));
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
this.problem.render(codeinputProblemHtml);
|
||||
});
|
||||
|
||||
it('has rendered with correct a11y info', function() {
|
||||
const CodeMirrorTextArea = $('textarea')[1];
|
||||
const CodeMirrorTextAreaId = 'cm-textarea-101';
|
||||
it("has rendered with correct a11y info", function () {
|
||||
const CodeMirrorTextArea = $("textarea")[1];
|
||||
const CodeMirrorTextAreaId = "cm-textarea-101";
|
||||
|
||||
// verify that question label has correct `for` attribute value
|
||||
expect($('.problem-group-label').attr('for')).toEqual(CodeMirrorTextAreaId);
|
||||
expect($(".problem-group-label").attr("for")).toEqual(CodeMirrorTextAreaId);
|
||||
|
||||
// verify that codemirror textarea has correct `id` attribute value
|
||||
expect($(CodeMirrorTextArea).attr('id')).toEqual(CodeMirrorTextAreaId);
|
||||
expect($(CodeMirrorTextArea).attr("id")).toEqual(CodeMirrorTextAreaId);
|
||||
|
||||
// verify that codemirror textarea has correct `aria-describedby` attribute value
|
||||
expect($(CodeMirrorTextArea).attr('aria-describedby')).toEqual('cm-editor-exit-message-101 status_101');
|
||||
expect($(CodeMirrorTextArea).attr("aria-describedby")).toEqual("cm-editor-exit-message-101 status_101");
|
||||
});
|
||||
});
|
||||
|
||||
describe("show answer button", function () {
|
||||
const radioButtonProblemHtml = readFixtures("radiobutton_problem.html");
|
||||
const checkboxProblemHtml = readFixtures("checkbox_problem.html");
|
||||
|
||||
describe('show answer button', function() {
|
||||
|
||||
const radioButtonProblemHtml = readFixtures('radiobutton_problem.html');
|
||||
const checkboxProblemHtml = readFixtures('checkbox_problem.html');
|
||||
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
beforeEach(function () {
|
||||
this.problem = new Problem($(".xblock-student_view"));
|
||||
|
||||
this.checkAssertionsAfterClickingAnotherOption = () => {
|
||||
// verify that 'show answer button is no longer disabled'
|
||||
expect(this.problem.el.find('.show').attr('disabled')).not.toEqual('disabled');
|
||||
expect(this.problem.el.find(".show").attr("disabled")).not.toEqual("disabled");
|
||||
|
||||
// verify that displayed answer disappears
|
||||
expect(this.problem.el.find('div.choicegroup')).not.toHaveClass('choicegroup_correct');
|
||||
expect(this.problem.el.find("div.choicegroup")).not.toHaveClass("choicegroup_correct");
|
||||
|
||||
// verify that radio/checkbox label has no span having class '.status.correct'
|
||||
expect(this.problem.el.find('div.choicegroup')).not.toHaveAttr('span.status.correct');
|
||||
expect(this.problem.el.find("div.choicegroup")).not.toHaveAttr("span.status.correct");
|
||||
};
|
||||
});
|
||||
|
||||
it('should become enabled after a radiobutton is selected', function() {
|
||||
$('#input_example_1').replaceWith(radioButtonProblemHtml);
|
||||
it("should become enabled after a radiobutton is selected", function () {
|
||||
$("#input_example_1").replaceWith(radioButtonProblemHtml);
|
||||
// assume that 'ShowAnswer' button is clicked,
|
||||
// clicking make it disabled.
|
||||
this.problem.el.find('.show').attr('disabled', 'disabled');
|
||||
this.problem.el.find(".show").attr("disabled", "disabled");
|
||||
// bind click event to input fields
|
||||
this.problem.submitAnswersAndSubmitButton(true);
|
||||
// selects option 2
|
||||
$('#input_1_1_2').attr('checked', true).trigger('click');
|
||||
$("#input_1_1_2").attr("checked", true).trigger("click");
|
||||
this.checkAssertionsAfterClickingAnotherOption();
|
||||
});
|
||||
|
||||
it('should become enabled after a checkbox is selected', function() {
|
||||
$('#input_example_1').replaceWith(checkboxProblemHtml);
|
||||
this.problem.el.find('.show').attr('disabled', 'disabled');
|
||||
it("should become enabled after a checkbox is selected", function () {
|
||||
$("#input_example_1").replaceWith(checkboxProblemHtml);
|
||||
this.problem.el.find(".show").attr("disabled", "disabled");
|
||||
this.problem.submitAnswersAndSubmitButton(true);
|
||||
$('#input_1_1_2').click();
|
||||
$("#input_1_1_2").click();
|
||||
this.checkAssertionsAfterClickingAnotherOption();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,133 +6,127 @@
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line no-shadow-restricted-names
|
||||
(function($, ImageInput, undefined) {
|
||||
describe('ImageInput', function() {
|
||||
var state;
|
||||
(function ($, ImageInput, undefined) {
|
||||
describe("ImageInput", function () {
|
||||
var state;
|
||||
|
||||
beforeEach(function() {
|
||||
var $el;
|
||||
beforeEach(function () {
|
||||
var $el;
|
||||
|
||||
loadFixtures('imageinput.html');
|
||||
$el = $('#imageinput_12345');
|
||||
loadFixtures("imageinput.html");
|
||||
$el = $("#imageinput_12345");
|
||||
|
||||
$el.append(createTestImage('cross_12345', 300, 400, 'red'));
|
||||
$el.append(createTestImage("cross_12345", 300, 400, "red"));
|
||||
|
||||
state = new ImageInput('12345');
|
||||
});
|
||||
|
||||
it('initialization', function() {
|
||||
// Check that object's properties are present, and that the DOM
|
||||
// elements they reference exist.
|
||||
expect(state.el).toBeDefined();
|
||||
expect(state.el).toExist();
|
||||
|
||||
expect(state.crossEl).toBeDefined();
|
||||
expect(state.crossEl).toExist();
|
||||
|
||||
expect(state.inputEl).toBeDefined();
|
||||
expect(state.inputEl).toExist();
|
||||
|
||||
expect(state.el).toHandle('click');
|
||||
});
|
||||
|
||||
it('cross becomes visible after first click', function() {
|
||||
expect(state.crossEl.css('visibility')).toBe('hidden');
|
||||
|
||||
state.el.click();
|
||||
|
||||
expect(state.crossEl.css('visibility')).toBe('visible');
|
||||
});
|
||||
|
||||
it('coordinates are updated [offsetX is set]', function() {
|
||||
var event, posX, posY, cssLeft, cssTop;
|
||||
|
||||
// Set up of 'click' event.
|
||||
event = jQuery.Event(
|
||||
'click',
|
||||
{offsetX: 35.3, offsetY: 42.7}
|
||||
);
|
||||
|
||||
// Calculating the expected coordinates.
|
||||
posX = event.offsetX;
|
||||
posY = event.offsetY;
|
||||
|
||||
// Triggering 'click' event.
|
||||
jQuery(state.el).trigger(event);
|
||||
|
||||
// Getting actual (new) coordinates, and testing them against the
|
||||
// expected.
|
||||
cssLeft = stripPx(state.crossEl.css('left'));
|
||||
cssTop = stripPx(state.crossEl.css('top'));
|
||||
|
||||
expect(cssLeft).toBeCloseTo(posX - 15, 1);
|
||||
expect(cssTop).toBeCloseTo(posY - 15, 1);
|
||||
expect(state.inputEl.val()).toBe(
|
||||
'[' + Math.round(posX) + ',' + Math.round(posY) + ']'
|
||||
);
|
||||
});
|
||||
|
||||
it('coordinates are updated [offsetX is NOT set]', function() {
|
||||
var offset = state.el.offset(),
|
||||
event, posX, posY, cssLeft, cssTop;
|
||||
|
||||
// Set up of 'click' event.
|
||||
event = jQuery.Event(
|
||||
'click',
|
||||
{
|
||||
offsetX: undefined,
|
||||
offsetY: undefined,
|
||||
pageX: 35.3,
|
||||
pageY: 42.7
|
||||
}
|
||||
);
|
||||
|
||||
// Calculating the expected coordinates.
|
||||
posX = event.pageX - offset.left;
|
||||
posY = event.pageY - offset.top;
|
||||
|
||||
// Triggering 'click' event.
|
||||
jQuery(state.el).trigger(event);
|
||||
|
||||
// Getting actual (new) coordinates, and testing them against the
|
||||
// expected.
|
||||
cssLeft = stripPx(state.crossEl.css('left'));
|
||||
cssTop = stripPx(state.crossEl.css('top'));
|
||||
|
||||
expect(cssLeft).toBeCloseTo(posX - 15, 1);
|
||||
expect(cssTop).toBeCloseTo(posY - 15, 1);
|
||||
expect(state.inputEl.val()).toBe(
|
||||
'[' + Math.round(posX) + ',' + Math.round(posY) + ']'
|
||||
);
|
||||
});
|
||||
state = new ImageInput("12345");
|
||||
});
|
||||
|
||||
// Instead of storing an image, and then including it in the template via
|
||||
// the <img /> tag, we will generate one on the fly.
|
||||
//
|
||||
// Create a simple image from a canvas. The canvas is filled by a colored
|
||||
// rectangle.
|
||||
function createTestImage(id, width, height, fillStyle) {
|
||||
var canvas, ctx, img;
|
||||
it("initialization", function () {
|
||||
// Check that object's properties are present, and that the DOM
|
||||
// elements they reference exist.
|
||||
expect(state.el).toBeDefined();
|
||||
expect(state.el).toExist();
|
||||
|
||||
canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
expect(state.crossEl).toBeDefined();
|
||||
expect(state.crossEl).toExist();
|
||||
|
||||
ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
expect(state.inputEl).toBeDefined();
|
||||
expect(state.inputEl).toExist();
|
||||
|
||||
img = document.createElement('img');
|
||||
img.src = canvas.toDataURL('image/png');
|
||||
img.id = id;
|
||||
expect(state.el).toHandle("click");
|
||||
});
|
||||
|
||||
return img;
|
||||
}
|
||||
it("cross becomes visible after first click", function () {
|
||||
expect(state.crossEl.css("visibility")).toBe("hidden");
|
||||
|
||||
// Strip the trailing 'px' substring from a CSS string containing the
|
||||
// `left` and `top` properties of an element's style.
|
||||
function stripPx(str) {
|
||||
return str.substring(0, str.length - 2);
|
||||
}
|
||||
state.el.click();
|
||||
|
||||
expect(state.crossEl.css("visibility")).toBe("visible");
|
||||
});
|
||||
|
||||
it("coordinates are updated [offsetX is set]", function () {
|
||||
var event, posX, posY, cssLeft, cssTop;
|
||||
|
||||
// Set up of 'click' event.
|
||||
event = jQuery.Event("click", { offsetX: 35.3, offsetY: 42.7 });
|
||||
|
||||
// Calculating the expected coordinates.
|
||||
posX = event.offsetX;
|
||||
posY = event.offsetY;
|
||||
|
||||
// Triggering 'click' event.
|
||||
jQuery(state.el).trigger(event);
|
||||
|
||||
// Getting actual (new) coordinates, and testing them against the
|
||||
// expected.
|
||||
cssLeft = stripPx(state.crossEl.css("left"));
|
||||
cssTop = stripPx(state.crossEl.css("top"));
|
||||
|
||||
expect(cssLeft).toBeCloseTo(posX - 15, 1);
|
||||
expect(cssTop).toBeCloseTo(posY - 15, 1);
|
||||
expect(state.inputEl.val()).toBe("[" + Math.round(posX) + "," + Math.round(posY) + "]");
|
||||
});
|
||||
|
||||
it("coordinates are updated [offsetX is NOT set]", function () {
|
||||
var offset = state.el.offset(),
|
||||
event,
|
||||
posX,
|
||||
posY,
|
||||
cssLeft,
|
||||
cssTop;
|
||||
|
||||
// Set up of 'click' event.
|
||||
event = jQuery.Event("click", {
|
||||
offsetX: undefined,
|
||||
offsetY: undefined,
|
||||
pageX: 35.3,
|
||||
pageY: 42.7,
|
||||
});
|
||||
|
||||
// Calculating the expected coordinates.
|
||||
posX = event.pageX - offset.left;
|
||||
posY = event.pageY - offset.top;
|
||||
|
||||
// Triggering 'click' event.
|
||||
jQuery(state.el).trigger(event);
|
||||
|
||||
// Getting actual (new) coordinates, and testing them against the
|
||||
// expected.
|
||||
cssLeft = stripPx(state.crossEl.css("left"));
|
||||
cssTop = stripPx(state.crossEl.css("top"));
|
||||
|
||||
expect(cssLeft).toBeCloseTo(posX - 15, 1);
|
||||
expect(cssTop).toBeCloseTo(posY - 15, 1);
|
||||
expect(state.inputEl.val()).toBe("[" + Math.round(posX) + "," + Math.round(posY) + "]");
|
||||
});
|
||||
});
|
||||
|
||||
// Instead of storing an image, and then including it in the template via
|
||||
// the <img /> tag, we will generate one on the fly.
|
||||
//
|
||||
// Create a simple image from a canvas. The canvas is filled by a colored
|
||||
// rectangle.
|
||||
function createTestImage(id, width, height, fillStyle) {
|
||||
var canvas, ctx, img;
|
||||
|
||||
canvas = document.createElement("canvas");
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
ctx = canvas.getContext("2d");
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
img = document.createElement("img");
|
||||
img.src = canvas.toDataURL("image/png");
|
||||
img.id = id;
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
// Strip the trailing 'px' substring from a CSS string containing the
|
||||
// `left` and `top` properties of an element's style.
|
||||
function stripPx(str) {
|
||||
return str.substring(0, str.length - 2);
|
||||
}
|
||||
}).call(this, window.jQuery, window.ImageInput);
|
||||
|
||||
@@ -1,126 +1,130 @@
|
||||
// eslint-disable-next-line no-shadow-restricted-names
|
||||
(function(undefined) {
|
||||
'use strict';
|
||||
(function (undefined) {
|
||||
"use strict";
|
||||
|
||||
describe('Collapsible', function() {
|
||||
var $el, html, html_custom,
|
||||
initialize = function(template) {
|
||||
setFixtures(template);
|
||||
$el = $('.collapsible');
|
||||
Collapsible.setCollapsibles($el);
|
||||
},
|
||||
disableFx = function() {
|
||||
$.fx.off = true;
|
||||
},
|
||||
enableFx = function() {
|
||||
$.fx.off = false;
|
||||
};
|
||||
describe("Collapsible", function () {
|
||||
var $el,
|
||||
html,
|
||||
html_custom,
|
||||
initialize = function (template) {
|
||||
setFixtures(template);
|
||||
$el = $(".collapsible");
|
||||
Collapsible.setCollapsibles($el);
|
||||
},
|
||||
disableFx = function () {
|
||||
$.fx.off = true;
|
||||
},
|
||||
enableFx = function () {
|
||||
$.fx.off = false;
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
html = ''
|
||||
+ '<section class="collapsible">'
|
||||
+ '<div class="shortform">shortform message</div>'
|
||||
+ '<div class="longform">'
|
||||
+ '<p>longform is visible</p>'
|
||||
+ '</div>'
|
||||
+ '</section>';
|
||||
html_custom = ''
|
||||
+ '<section class="collapsible">'
|
||||
+ '<div '
|
||||
+ 'class="shortform-custom" '
|
||||
+ 'data-open-text="Show shortform-custom" '
|
||||
+ 'data-close-text="Hide shortform-custom"'
|
||||
+ '>shortform message</div>'
|
||||
+ '<div class="longform">'
|
||||
+ '<p>longform is visible</p>'
|
||||
+ '</div>'
|
||||
+ '</section>';
|
||||
});
|
||||
|
||||
describe('setCollapsibles', function() {
|
||||
it('Default container initialized correctly', function() {
|
||||
initialize(html);
|
||||
|
||||
expect($el.find('.shortform')).toContainElement('.full-top');
|
||||
expect($el.find('.shortform')).toContainElement('.full-bottom');
|
||||
expect($el.find('.longform')).toBeHidden();
|
||||
expect($el.find('.full')).toHandle('click');
|
||||
});
|
||||
|
||||
it('Custom container initialized correctly', function() {
|
||||
initialize(html_custom);
|
||||
|
||||
expect($el.find('.shortform-custom')).toContainElement('.full-custom');
|
||||
expect($el.find('.full-custom')).toHaveText('Show shortform-custom');
|
||||
expect($el.find('.longform')).toBeHidden();
|
||||
expect($el.find('.full-custom')).toHandle('click');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleFull', function() {
|
||||
var assertChanges = function(state, anchorsElClass, showText, hideText) {
|
||||
var anchors, text;
|
||||
|
||||
if (state == null) {
|
||||
state = 'closed';
|
||||
}
|
||||
|
||||
anchors = $el.find('.' + anchorsElClass);
|
||||
|
||||
if (state === 'closed') {
|
||||
expect($el.find('.longform')).toBeHidden();
|
||||
expect($el).not.toHaveClass('open');
|
||||
text = showText;
|
||||
} else {
|
||||
expect($el.find('.longform')).toBeVisible();
|
||||
expect($el).toHaveClass('open');
|
||||
text = hideText;
|
||||
}
|
||||
|
||||
$.each(anchors, function(index, el) {
|
||||
expect(el).toHaveText(text);
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
disableFx();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
enableFx();
|
||||
});
|
||||
|
||||
it('Default container', function() {
|
||||
var event;
|
||||
|
||||
initialize(html);
|
||||
|
||||
event = jQuery.Event('click', {
|
||||
target: $el.find('.full').get(0)
|
||||
});
|
||||
|
||||
Collapsible.toggleFull(event, 'See full output', 'Hide output');
|
||||
assertChanges('opened', 'full', 'See full output', 'Hide output');
|
||||
|
||||
Collapsible.toggleFull(event, 'See full output', 'Hide output');
|
||||
assertChanges('closed', 'full', 'See full output', 'Hide output');
|
||||
});
|
||||
|
||||
it('Custom container', function() {
|
||||
var event;
|
||||
|
||||
initialize(html_custom);
|
||||
|
||||
event = jQuery.Event('click', {
|
||||
target: $el.find('.full-custom').get(0)
|
||||
});
|
||||
|
||||
Collapsible.toggleFull(event, 'Show shortform-custom', 'Hide shortform-custom');
|
||||
assertChanges('opened', 'full-custom', 'Show shortform-custom', 'Hide shortform-custom');
|
||||
|
||||
Collapsible.toggleFull(event, 'Show shortform-custom', 'Hide shortform-custom');
|
||||
assertChanges('closed', 'full-custom', 'Show shortform-custom', 'Hide shortform-custom');
|
||||
});
|
||||
});
|
||||
beforeEach(function () {
|
||||
html =
|
||||
"" +
|
||||
'<section class="collapsible">' +
|
||||
'<div class="shortform">shortform message</div>' +
|
||||
'<div class="longform">' +
|
||||
"<p>longform is visible</p>" +
|
||||
"</div>" +
|
||||
"</section>";
|
||||
html_custom =
|
||||
"" +
|
||||
'<section class="collapsible">' +
|
||||
"<div " +
|
||||
'class="shortform-custom" ' +
|
||||
'data-open-text="Show shortform-custom" ' +
|
||||
'data-close-text="Hide shortform-custom"' +
|
||||
">shortform message</div>" +
|
||||
'<div class="longform">' +
|
||||
"<p>longform is visible</p>" +
|
||||
"</div>" +
|
||||
"</section>";
|
||||
});
|
||||
|
||||
describe("setCollapsibles", function () {
|
||||
it("Default container initialized correctly", function () {
|
||||
initialize(html);
|
||||
|
||||
expect($el.find(".shortform")).toContainElement(".full-top");
|
||||
expect($el.find(".shortform")).toContainElement(".full-bottom");
|
||||
expect($el.find(".longform")).toBeHidden();
|
||||
expect($el.find(".full")).toHandle("click");
|
||||
});
|
||||
|
||||
it("Custom container initialized correctly", function () {
|
||||
initialize(html_custom);
|
||||
|
||||
expect($el.find(".shortform-custom")).toContainElement(".full-custom");
|
||||
expect($el.find(".full-custom")).toHaveText("Show shortform-custom");
|
||||
expect($el.find(".longform")).toBeHidden();
|
||||
expect($el.find(".full-custom")).toHandle("click");
|
||||
});
|
||||
});
|
||||
|
||||
describe("toggleFull", function () {
|
||||
var assertChanges = function (state, anchorsElClass, showText, hideText) {
|
||||
var anchors, text;
|
||||
|
||||
if (state == null) {
|
||||
state = "closed";
|
||||
}
|
||||
|
||||
anchors = $el.find("." + anchorsElClass);
|
||||
|
||||
if (state === "closed") {
|
||||
expect($el.find(".longform")).toBeHidden();
|
||||
expect($el).not.toHaveClass("open");
|
||||
text = showText;
|
||||
} else {
|
||||
expect($el.find(".longform")).toBeVisible();
|
||||
expect($el).toHaveClass("open");
|
||||
text = hideText;
|
||||
}
|
||||
|
||||
$.each(anchors, function (index, el) {
|
||||
expect(el).toHaveText(text);
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
disableFx();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
enableFx();
|
||||
});
|
||||
|
||||
it("Default container", function () {
|
||||
var event;
|
||||
|
||||
initialize(html);
|
||||
|
||||
event = jQuery.Event("click", {
|
||||
target: $el.find(".full").get(0),
|
||||
});
|
||||
|
||||
Collapsible.toggleFull(event, "See full output", "Hide output");
|
||||
assertChanges("opened", "full", "See full output", "Hide output");
|
||||
|
||||
Collapsible.toggleFull(event, "See full output", "Hide output");
|
||||
assertChanges("closed", "full", "See full output", "Hide output");
|
||||
});
|
||||
|
||||
it("Custom container", function () {
|
||||
var event;
|
||||
|
||||
initialize(html_custom);
|
||||
|
||||
event = jQuery.Event("click", {
|
||||
target: $el.find(".full-custom").get(0),
|
||||
});
|
||||
|
||||
Collapsible.toggleFull(event, "Show shortform-custom", "Hide shortform-custom");
|
||||
assertChanges("opened", "full-custom", "Show shortform-custom", "Hide shortform-custom");
|
||||
|
||||
Collapsible.toggleFull(event, "Show shortform-custom", "Hide shortform-custom");
|
||||
assertChanges("closed", "full-custom", "Show shortform-custom", "Hide shortform-custom");
|
||||
});
|
||||
});
|
||||
});
|
||||
}).call(this);
|
||||
|
||||
@@ -1,335 +1,325 @@
|
||||
/* global _ */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var origAjax = $.ajax;
|
||||
var origAjax = $.ajax;
|
||||
|
||||
var stubbedYT = {
|
||||
Player: function() {
|
||||
var Player = jasmine.createSpyObj(
|
||||
'YT.Player',
|
||||
[
|
||||
'cueVideoById', 'getVideoEmbedCode', 'getCurrentTime',
|
||||
'getPlayerState', 'getVolume', 'setVolume',
|
||||
'loadVideoById', 'getAvailablePlaybackRates', 'playVideo',
|
||||
'pauseVideo', 'seekTo', 'getDuration', 'setPlaybackRate',
|
||||
'getAvailableQualityLevels', 'getPlaybackQuality',
|
||||
'setPlaybackQuality', 'destroy'
|
||||
]
|
||||
);
|
||||
var stubbedYT = {
|
||||
Player: function () {
|
||||
var Player = jasmine.createSpyObj("YT.Player", [
|
||||
"cueVideoById",
|
||||
"getVideoEmbedCode",
|
||||
"getCurrentTime",
|
||||
"getPlayerState",
|
||||
"getVolume",
|
||||
"setVolume",
|
||||
"loadVideoById",
|
||||
"getAvailablePlaybackRates",
|
||||
"playVideo",
|
||||
"pauseVideo",
|
||||
"seekTo",
|
||||
"getDuration",
|
||||
"setPlaybackRate",
|
||||
"getAvailableQualityLevels",
|
||||
"getPlaybackQuality",
|
||||
"setPlaybackQuality",
|
||||
"destroy",
|
||||
]);
|
||||
|
||||
Player.getDuration.and.returnValue(60);
|
||||
Player.getAvailablePlaybackRates.and.returnValue([0.50, 1.0, 1.50, 2.0]);
|
||||
Player.getAvailableQualityLevels.and.returnValue(
|
||||
['highres', 'hd1080', 'hd720', 'large', 'medium', 'small']
|
||||
);
|
||||
Player.getDuration.and.returnValue(60);
|
||||
Player.getAvailablePlaybackRates.and.returnValue([0.5, 1.0, 1.5, 2.0]);
|
||||
Player.getAvailableQualityLevels.and.returnValue(["highres", "hd1080", "hd720", "large", "medium", "small"]);
|
||||
|
||||
return Player;
|
||||
},
|
||||
return Player;
|
||||
},
|
||||
|
||||
PlayerState: {
|
||||
UNSTARTED: -1,
|
||||
ENDED: 0,
|
||||
PLAYING: 1,
|
||||
PAUSED: 2,
|
||||
BUFFERING: 3,
|
||||
CUED: 5
|
||||
},
|
||||
ready: function(f) {
|
||||
return f();
|
||||
}
|
||||
};
|
||||
jasmine.YT = stubbedYT;
|
||||
// Stub YouTube API.
|
||||
window.YT = stubbedYT;
|
||||
PlayerState: {
|
||||
UNSTARTED: -1,
|
||||
ENDED: 0,
|
||||
PLAYING: 1,
|
||||
PAUSED: 2,
|
||||
BUFFERING: 3,
|
||||
CUED: 5,
|
||||
},
|
||||
ready: function (f) {
|
||||
return f();
|
||||
},
|
||||
};
|
||||
jasmine.YT = stubbedYT;
|
||||
// Stub YouTube API.
|
||||
window.YT = stubbedYT;
|
||||
|
||||
window.STATUS = window.YT.PlayerState;
|
||||
window.STATUS = window.YT.PlayerState;
|
||||
|
||||
window.onTouchBasedDevice = function() {
|
||||
return navigator.userAgent.match(/iPhone|iPod|iPad/i);
|
||||
};
|
||||
window.onTouchBasedDevice = function () {
|
||||
return navigator.userAgent.match(/iPhone|iPod|iPad/i);
|
||||
};
|
||||
|
||||
jasmine.stubbedCaption = {
|
||||
end: [
|
||||
3120, 6270, 8490, 21620, 24920, 25750, 27900, 34380, 35550, 40250
|
||||
],
|
||||
start: [
|
||||
1180, 3120, 6270, 14910, 21620, 24920, 25750, 27900, 34380, 35550
|
||||
],
|
||||
text: [
|
||||
'MICHAEL CIMA: So let\'s do the first one here.',
|
||||
'Vacancies, where do they come from?',
|
||||
'Well, imagine a perfect crystal.',
|
||||
'Now we know at any temperature other than absolute zero '
|
||||
+ 'there\'s enough',
|
||||
'energy going around that some atoms will have more energy',
|
||||
'than others, right?',
|
||||
'There\'s a distribution.',
|
||||
'If I plot energy here and number, these atoms in the crystal '
|
||||
+ 'will have a',
|
||||
'distribution of energy.',
|
||||
'And some will have quite a bit of energy, just for a moment.'
|
||||
]
|
||||
};
|
||||
jasmine.stubbedCaption = {
|
||||
end: [3120, 6270, 8490, 21620, 24920, 25750, 27900, 34380, 35550, 40250],
|
||||
start: [1180, 3120, 6270, 14910, 21620, 24920, 25750, 27900, 34380, 35550],
|
||||
text: [
|
||||
"MICHAEL CIMA: So let's do the first one here.",
|
||||
"Vacancies, where do they come from?",
|
||||
"Well, imagine a perfect crystal.",
|
||||
"Now we know at any temperature other than absolute zero " + "there's enough",
|
||||
"energy going around that some atoms will have more energy",
|
||||
"than others, right?",
|
||||
"There's a distribution.",
|
||||
"If I plot energy here and number, these atoms in the crystal " + "will have a",
|
||||
"distribution of energy.",
|
||||
"And some will have quite a bit of energy, just for a moment.",
|
||||
],
|
||||
};
|
||||
|
||||
// Time waitsFor() should wait for before failing a test.
|
||||
window.WAIT_TIMEOUT = 5000;
|
||||
// Time waitsFor() should wait for before failing a test.
|
||||
window.WAIT_TIMEOUT = 5000;
|
||||
|
||||
jasmine.getFixtures().fixturesPath += 'fixtures';
|
||||
jasmine.getFixtures().fixturesPath += "fixtures";
|
||||
|
||||
jasmine.stubbedMetadata = {
|
||||
'7tqY6eQzVhE': {
|
||||
contentDetails: {
|
||||
id: '7tqY6eQzVhE',
|
||||
duration: 'PT5M0S'
|
||||
}
|
||||
},
|
||||
cogebirgzzM: {
|
||||
contentDetails: {
|
||||
id: 'cogebirgzzM',
|
||||
duration: 'PT3M20S'
|
||||
}
|
||||
},
|
||||
abcdefghijkl: {
|
||||
contentDetails: {
|
||||
id: 'abcdefghijkl',
|
||||
duration: 'PT6M40S'
|
||||
}
|
||||
},
|
||||
bogus: {
|
||||
contentDetails: {
|
||||
duration: 'PT1M40S'
|
||||
}
|
||||
}
|
||||
};
|
||||
jasmine.stubbedMetadata = {
|
||||
"7tqY6eQzVhE": {
|
||||
contentDetails: {
|
||||
id: "7tqY6eQzVhE",
|
||||
duration: "PT5M0S",
|
||||
},
|
||||
},
|
||||
cogebirgzzM: {
|
||||
contentDetails: {
|
||||
id: "cogebirgzzM",
|
||||
duration: "PT3M20S",
|
||||
},
|
||||
},
|
||||
abcdefghijkl: {
|
||||
contentDetails: {
|
||||
id: "abcdefghijkl",
|
||||
duration: "PT6M40S",
|
||||
},
|
||||
},
|
||||
bogus: {
|
||||
contentDetails: {
|
||||
duration: "PT1M40S",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
jasmine.fireEvent = function(el, eventName) {
|
||||
var event;
|
||||
jasmine.fireEvent = function (el, eventName) {
|
||||
var event;
|
||||
|
||||
if (document.createEvent) {
|
||||
event = document.createEvent('HTMLEvents');
|
||||
event.initEvent(eventName, true, true);
|
||||
if (document.createEvent) {
|
||||
event = document.createEvent("HTMLEvents");
|
||||
event.initEvent(eventName, true, true);
|
||||
} else {
|
||||
event = document.createEventObject();
|
||||
event.eventType = eventName;
|
||||
}
|
||||
|
||||
event.eventName = eventName;
|
||||
|
||||
if (document.createEvent) {
|
||||
el.dispatchEvent(event);
|
||||
} else {
|
||||
el.fireEvent("on" + event.eventType, event);
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.stubbedHtml5Speeds = ["0.75", "1.0", "1.25", "1.50", "2.0"];
|
||||
|
||||
jasmine.stubRequests = function () {
|
||||
var spy = $.ajax;
|
||||
if (!jasmine.isSpy($.ajax)) {
|
||||
spy = spyOn($, "ajax");
|
||||
}
|
||||
|
||||
return spy.and.callFake(function (settings) {
|
||||
var match = settings.url.match(/googleapis\.com\/.+\/videos\/\?id=(.+)&part=contentDetails/),
|
||||
status,
|
||||
callCallback;
|
||||
if (match) {
|
||||
status = match[1].split("_");
|
||||
if (status && status[0] === "status") {
|
||||
callCallback = function (callback) {
|
||||
callback.call(window, {}, status[1]);
|
||||
};
|
||||
|
||||
return {
|
||||
always: callCallback,
|
||||
error: callCallback,
|
||||
done: callCallback,
|
||||
};
|
||||
} else if (settings.success) {
|
||||
return settings.success({
|
||||
items: jasmine.stubbedMetadata[match[1]],
|
||||
});
|
||||
} else {
|
||||
event = document.createEventObject();
|
||||
event.eventType = eventName;
|
||||
return {
|
||||
always: function (callback) {
|
||||
return callback.call(window, {}, "success");
|
||||
},
|
||||
done: function (callback) {
|
||||
return callback.call(window, {}, "success");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
event.eventName = eventName;
|
||||
|
||||
if (document.createEvent) {
|
||||
el.dispatchEvent(event);
|
||||
} else {
|
||||
el.fireEvent('on' + event.eventType, event);
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.stubbedHtml5Speeds = ['0.75', '1.0', '1.25', '1.50', '2.0'];
|
||||
|
||||
jasmine.stubRequests = function() {
|
||||
var spy = $.ajax;
|
||||
if (!jasmine.isSpy($.ajax)) {
|
||||
spy = spyOn($, 'ajax');
|
||||
}
|
||||
|
||||
return spy.and.callFake(function(settings) {
|
||||
var match = settings.url
|
||||
.match(/googleapis\.com\/.+\/videos\/\?id=(.+)&part=contentDetails/),
|
||||
status, callCallback;
|
||||
if (match) {
|
||||
status = match[1].split('_');
|
||||
if (status && status[0] === 'status') {
|
||||
callCallback = function(callback) {
|
||||
callback.call(window, {}, status[1]);
|
||||
};
|
||||
|
||||
return {
|
||||
always: callCallback,
|
||||
error: callCallback,
|
||||
done: callCallback
|
||||
};
|
||||
} else if (settings.success) {
|
||||
return settings.success({
|
||||
items: jasmine.stubbedMetadata[match[1]]
|
||||
});
|
||||
} else {
|
||||
return {
|
||||
always: function(callback) {
|
||||
return callback.call(window, {}, 'success');
|
||||
},
|
||||
done: function(callback) {
|
||||
return callback.call(window, {}, 'success');
|
||||
}
|
||||
};
|
||||
}
|
||||
} else if (settings.url.match(/transcript\/translation\/.+$/)) {
|
||||
return settings.success(jasmine.stubbedCaption);
|
||||
} else if (settings.url === '/transcript/available_translations') {
|
||||
return settings.success(['uk', 'de']);
|
||||
} else if (settings.url.match(/.+\/problem_get$/)) {
|
||||
return settings.success({
|
||||
html: window.readFixtures('problem_content.html')
|
||||
});
|
||||
} else if (
|
||||
settings.url === '/calculate'
|
||||
|| settings.url.match(/.+\/goto_position$/)
|
||||
|| settings.url.match(/event$/)
|
||||
|| settings.url.match(/.+\/problem_(check|reset|show|save)$/)
|
||||
) {
|
||||
// Do nothing.
|
||||
return {};
|
||||
} else if (settings.url === '/save_user_state') {
|
||||
return {success: true};
|
||||
} else if (settings.url.match(/.+video-transcript.+$/)) {
|
||||
if (settings.url.match(/.+&video_id=notAIGenerated/)) {
|
||||
return settings.success(null);
|
||||
}
|
||||
if (settings.url.match(/.+&video_id=inProgress/)) {
|
||||
return settings.success({
|
||||
status: 'In Progress'
|
||||
});
|
||||
}
|
||||
if (settings.url.match(/.+&video_id=error/)) {
|
||||
return settings.error();
|
||||
}
|
||||
return settings.success({
|
||||
status: 'Completed'
|
||||
});
|
||||
} else if (settings.url.match(/.+transcript-feedback.+$/) && settings.type === 'GET') {
|
||||
if (settings.url.match(/.+&video_id=error.+$/)) {
|
||||
return settings.error();
|
||||
}
|
||||
if (settings.url.match(/.+&video_id=negative.+$/)) {
|
||||
return settings.success({
|
||||
value: false
|
||||
});
|
||||
}
|
||||
if (settings.url.match(/.+&video_id=none.+$/)) {
|
||||
return settings.success(null);
|
||||
}
|
||||
return settings.success({
|
||||
value: true
|
||||
});
|
||||
} else if (settings.url.match(/.+transcript-feedback.+$/) && settings.type === 'POST') {
|
||||
return settings.success(settings.data.value !== null ? { value: settings.data.value } : null);
|
||||
} else if (settings.url.match(new RegExp(jasmine.getFixtures().fixturesPath + '.+', 'g'))) {
|
||||
return origAjax(settings);
|
||||
} else {
|
||||
return $.ajax.and.callThrough();
|
||||
}
|
||||
} else if (settings.url.match(/transcript\/translation\/.+$/)) {
|
||||
return settings.success(jasmine.stubbedCaption);
|
||||
} else if (settings.url === "/transcript/available_translations") {
|
||||
return settings.success(["uk", "de"]);
|
||||
} else if (settings.url.match(/.+\/problem_get$/)) {
|
||||
return settings.success({
|
||||
html: window.readFixtures("problem_content.html"),
|
||||
});
|
||||
};
|
||||
|
||||
// Stub jQuery.cookie module.
|
||||
$.cookie = jasmine.createSpy('jQuery.cookie').and.returnValue('1.0');
|
||||
|
||||
// # Stub jQuery.qtip module.
|
||||
$.fn.qtip = jasmine.createSpy('jQuery.qtip');
|
||||
|
||||
// Stub jQuery.scrollTo module.
|
||||
$.fn.scrollTo = jasmine.createSpy('jQuery.scrollTo');
|
||||
|
||||
jasmine.initializePlayer = function(fixture, params) {
|
||||
var state, metadata;
|
||||
|
||||
if (_.isString(fixture)) {
|
||||
// `fixture` is a name of a fixture file.
|
||||
loadFixtures(fixture);
|
||||
} else {
|
||||
// `fixture` is not a string. The first parameter is an object?
|
||||
if (_.isObject(fixture)) {
|
||||
// The first parameter contains attributes for the main video
|
||||
// DIV element.
|
||||
params = fixture;
|
||||
}
|
||||
|
||||
// "video_all.html" is the default HTML template for HTML5 video.
|
||||
loadFixtures('video_all.html');
|
||||
} else if (
|
||||
settings.url === "/calculate" ||
|
||||
settings.url.match(/.+\/goto_position$/) ||
|
||||
settings.url.match(/event$/) ||
|
||||
settings.url.match(/.+\/problem_(check|reset|show|save)$/)
|
||||
) {
|
||||
// Do nothing.
|
||||
return {};
|
||||
} else if (settings.url === "/save_user_state") {
|
||||
return { success: true };
|
||||
} else if (settings.url.match(/.+video-transcript.+$/)) {
|
||||
if (settings.url.match(/.+&video_id=notAIGenerated/)) {
|
||||
return settings.success(null);
|
||||
}
|
||||
|
||||
// If `params` is an object, assign its properties as data attributes
|
||||
// to the main video DIV element.
|
||||
if (_.isObject(params)) {
|
||||
metadata = _.extend($('#video_id').data('metadata'), params);
|
||||
$('#video_id').data('metadata', metadata);
|
||||
if (settings.url.match(/.+&video_id=inProgress/)) {
|
||||
return settings.success({
|
||||
status: "In Progress",
|
||||
});
|
||||
}
|
||||
|
||||
jasmine.stubRequests();
|
||||
let runtime = jasmine.createSpyObj('TestRuntime', ['handlerUrl']);
|
||||
state = new window.Video(runtime, '#example');
|
||||
|
||||
state.resizer = (function() {
|
||||
var methods = [
|
||||
'align',
|
||||
'alignByWidthOnly',
|
||||
'alignByHeightOnly',
|
||||
'setParams',
|
||||
'setMode',
|
||||
'setElement'
|
||||
],
|
||||
obj = {},
|
||||
delta = {
|
||||
add: jasmine.createSpy().and.returnValue(obj),
|
||||
substract: jasmine.createSpy().and.returnValue(obj),
|
||||
reset: jasmine.createSpy().and.returnValue(obj)
|
||||
};
|
||||
|
||||
$.each(methods, function(index, method) {
|
||||
obj[method] = jasmine.createSpy(method).and.returnValue(obj);
|
||||
});
|
||||
|
||||
obj.delta = delta;
|
||||
|
||||
return obj;
|
||||
}());
|
||||
|
||||
// We return the `state` object of the newly initialized Video.
|
||||
return state;
|
||||
};
|
||||
|
||||
jasmine.initializeHLSPlayer = function(params) {
|
||||
return jasmine.initializePlayer('video_hls.html', params);
|
||||
};
|
||||
|
||||
jasmine.initializePlayerYouTube = function(params) {
|
||||
// "video.html" contains HTML template for a YouTube video.
|
||||
return jasmine.initializePlayer('video.html', params);
|
||||
};
|
||||
|
||||
jasmine.DescribeInfo = function(description, specDefinitions) {
|
||||
this.description = description;
|
||||
this.specDefinitions = specDefinitions;
|
||||
};
|
||||
|
||||
// This HTML Fullscreen API mock should use promises or async functions
|
||||
// as the spec defines. We do not use them here because we're locked
|
||||
// in to a version of jasmine that doesn't fully support async functions
|
||||
// or promises. This mock also assumes that if non-vendor prefixed methods
|
||||
// and properties are missing, then we'll use mozilla prefixed names since
|
||||
// automated tests happen in firefox.
|
||||
jasmine.mockFullscreenAPI = function() {
|
||||
var fullscreenElement;
|
||||
var vendorChangeEvent = 'fullscreenEnabled' in document
|
||||
? 'fullscreenchange' : 'mozfullscreenchange';
|
||||
var vendorRequestFullscreen = 'requestFullscreen' in window.HTMLElement.prototype
|
||||
? 'requestFullscreen' : 'mozRequestFullScreen';
|
||||
var vendorExitFullscreen = 'exitFullscreen' in document
|
||||
? 'exitFullscreen' : 'mozCancelFullScreen';
|
||||
var vendorFullscreenElement = 'fullscreenEnabled' in document
|
||||
? 'fullscreenElement' : 'mozFullScreenElement';
|
||||
|
||||
spyOn(window.HTMLElement.prototype, vendorRequestFullscreen).and.callFake(function() {
|
||||
fullscreenElement = this;
|
||||
document.dispatchEvent(new Event(vendorChangeEvent));
|
||||
if (settings.url.match(/.+&video_id=error/)) {
|
||||
return settings.error();
|
||||
}
|
||||
return settings.success({
|
||||
status: "Completed",
|
||||
});
|
||||
|
||||
spyOn(document, vendorExitFullscreen).and.callFake(function() {
|
||||
fullscreenElement = null;
|
||||
document.dispatchEvent(new Event(vendorChangeEvent));
|
||||
} else if (settings.url.match(/.+transcript-feedback.+$/) && settings.type === "GET") {
|
||||
if (settings.url.match(/.+&video_id=error.+$/)) {
|
||||
return settings.error();
|
||||
}
|
||||
if (settings.url.match(/.+&video_id=negative.+$/)) {
|
||||
return settings.success({
|
||||
value: false,
|
||||
});
|
||||
}
|
||||
if (settings.url.match(/.+&video_id=none.+$/)) {
|
||||
return settings.success(null);
|
||||
}
|
||||
return settings.success({
|
||||
value: true,
|
||||
});
|
||||
} else if (settings.url.match(/.+transcript-feedback.+$/) && settings.type === "POST") {
|
||||
return settings.success(settings.data.value !== null ? { value: settings.data.value } : null);
|
||||
} else if (settings.url.match(new RegExp(jasmine.getFixtures().fixturesPath + ".+", "g"))) {
|
||||
return origAjax(settings);
|
||||
} else {
|
||||
return $.ajax.and.callThrough();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
spyOnProperty(document, vendorFullscreenElement).and.callFake(function() {
|
||||
return fullscreenElement;
|
||||
});
|
||||
};
|
||||
// Stub jQuery.cookie module.
|
||||
$.cookie = jasmine.createSpy("jQuery.cookie").and.returnValue("1.0");
|
||||
|
||||
// # Stub jQuery.qtip module.
|
||||
$.fn.qtip = jasmine.createSpy("jQuery.qtip");
|
||||
|
||||
// Stub jQuery.scrollTo module.
|
||||
$.fn.scrollTo = jasmine.createSpy("jQuery.scrollTo");
|
||||
|
||||
jasmine.initializePlayer = function (fixture, params) {
|
||||
var state, metadata;
|
||||
|
||||
if (_.isString(fixture)) {
|
||||
// `fixture` is a name of a fixture file.
|
||||
loadFixtures(fixture);
|
||||
} else {
|
||||
// `fixture` is not a string. The first parameter is an object?
|
||||
if (_.isObject(fixture)) {
|
||||
// The first parameter contains attributes for the main video
|
||||
// DIV element.
|
||||
params = fixture;
|
||||
}
|
||||
|
||||
// "video_all.html" is the default HTML template for HTML5 video.
|
||||
loadFixtures("video_all.html");
|
||||
}
|
||||
|
||||
// If `params` is an object, assign its properties as data attributes
|
||||
// to the main video DIV element.
|
||||
if (_.isObject(params)) {
|
||||
metadata = _.extend($("#video_id").data("metadata"), params);
|
||||
$("#video_id").data("metadata", metadata);
|
||||
}
|
||||
|
||||
jasmine.stubRequests();
|
||||
let runtime = jasmine.createSpyObj("TestRuntime", ["handlerUrl"]);
|
||||
state = new window.Video(runtime, "#example");
|
||||
|
||||
state.resizer = (function () {
|
||||
var methods = ["align", "alignByWidthOnly", "alignByHeightOnly", "setParams", "setMode", "setElement"],
|
||||
obj = {},
|
||||
delta = {
|
||||
add: jasmine.createSpy().and.returnValue(obj),
|
||||
substract: jasmine.createSpy().and.returnValue(obj),
|
||||
reset: jasmine.createSpy().and.returnValue(obj),
|
||||
};
|
||||
|
||||
$.each(methods, function (index, method) {
|
||||
obj[method] = jasmine.createSpy(method).and.returnValue(obj);
|
||||
});
|
||||
|
||||
obj.delta = delta;
|
||||
|
||||
return obj;
|
||||
})();
|
||||
|
||||
// We return the `state` object of the newly initialized Video.
|
||||
return state;
|
||||
};
|
||||
|
||||
jasmine.initializeHLSPlayer = function (params) {
|
||||
return jasmine.initializePlayer("video_hls.html", params);
|
||||
};
|
||||
|
||||
jasmine.initializePlayerYouTube = function (params) {
|
||||
// "video.html" contains HTML template for a YouTube video.
|
||||
return jasmine.initializePlayer("video.html", params);
|
||||
};
|
||||
|
||||
jasmine.DescribeInfo = function (description, specDefinitions) {
|
||||
this.description = description;
|
||||
this.specDefinitions = specDefinitions;
|
||||
};
|
||||
|
||||
// This HTML Fullscreen API mock should use promises or async functions
|
||||
// as the spec defines. We do not use them here because we're locked
|
||||
// in to a version of jasmine that doesn't fully support async functions
|
||||
// or promises. This mock also assumes that if non-vendor prefixed methods
|
||||
// and properties are missing, then we'll use mozilla prefixed names since
|
||||
// automated tests happen in firefox.
|
||||
jasmine.mockFullscreenAPI = function () {
|
||||
var fullscreenElement;
|
||||
var vendorChangeEvent = "fullscreenEnabled" in document ? "fullscreenchange" : "mozfullscreenchange";
|
||||
var vendorRequestFullscreen =
|
||||
"requestFullscreen" in window.HTMLElement.prototype ? "requestFullscreen" : "mozRequestFullScreen";
|
||||
var vendorExitFullscreen = "exitFullscreen" in document ? "exitFullscreen" : "mozCancelFullScreen";
|
||||
var vendorFullscreenElement = "fullscreenEnabled" in document ? "fullscreenElement" : "mozFullScreenElement";
|
||||
|
||||
spyOn(window.HTMLElement.prototype, vendorRequestFullscreen).and.callFake(function () {
|
||||
fullscreenElement = this;
|
||||
document.dispatchEvent(new Event(vendorChangeEvent));
|
||||
});
|
||||
|
||||
spyOn(document, vendorExitFullscreen).and.callFake(function () {
|
||||
fullscreenElement = null;
|
||||
document.dispatchEvent(new Event(vendorChangeEvent));
|
||||
});
|
||||
|
||||
spyOnProperty(document, vendorFullscreenElement).and.callFake(function () {
|
||||
return fullscreenElement;
|
||||
});
|
||||
};
|
||||
}).call(this);
|
||||
|
||||
@@ -5,1354 +5,1407 @@
|
||||
// the max line length of 120.
|
||||
/* eslint max-len: ["error", 120, { "ignoreComments": true }] */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var indexOfHelper = [].indexOf
|
||||
|| function(item) {
|
||||
var i, len;
|
||||
for (i = 0, len = this.length; i < len; i++) {
|
||||
if (i in this && this[i] === item) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
this.Problem = (function() {
|
||||
function Problem(element) {
|
||||
var that = this;
|
||||
this.hint_button = function() {
|
||||
return Problem.prototype.hint_button.apply(that, arguments);
|
||||
};
|
||||
this.enableSubmitButtonAfterTimeout = function() {
|
||||
return Problem.prototype.enableSubmitButtonAfterTimeout.apply(that, arguments);
|
||||
};
|
||||
this.enableSubmitButtonAfterResponse = function() {
|
||||
return Problem.prototype.enableSubmitButtonAfterResponse.apply(that, arguments);
|
||||
};
|
||||
this.enableSubmitButton = function(enable, changeText) {
|
||||
if (changeText === null || changeText === undefined) {
|
||||
changeText = true; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
return Problem.prototype.enableSubmitButton.apply(that, arguments);
|
||||
};
|
||||
this.disableAllButtonsWhileRunning = function(
|
||||
operationCallback, isFromCheckOperation // eslint-disable-line no-unused-vars
|
||||
) {
|
||||
return Problem.prototype.disableAllButtonsWhileRunning.apply(that, arguments);
|
||||
};
|
||||
this.submitAnswersAndSubmitButton = function(bind) {
|
||||
if (bind === null || bind === undefined) {
|
||||
bind = false; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
return Problem.prototype.submitAnswersAndSubmitButton.apply(that, arguments);
|
||||
};
|
||||
this.refreshAnswers = function() {
|
||||
return Problem.prototype.refreshAnswers.apply(that, arguments);
|
||||
};
|
||||
this.updateMathML = function(jax, el) { // eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.updateMathML.apply(that, arguments);
|
||||
};
|
||||
this.refreshMath = function(event, el) { // eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.refreshMath.apply(that, arguments);
|
||||
};
|
||||
this.save_internal = function() {
|
||||
return Problem.prototype.save_internal.apply(that, arguments);
|
||||
};
|
||||
this.save = function() {
|
||||
return Problem.prototype.save.apply(that, arguments);
|
||||
};
|
||||
this.gentle_alert = function(msg) { // eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.gentle_alert.apply(that, arguments);
|
||||
};
|
||||
this.clear_all_notifications = function() {
|
||||
return Problem.prototype.clear_all_notifications.apply(that, arguments);
|
||||
};
|
||||
this.show = function() {
|
||||
return Problem.prototype.show.apply(that, arguments);
|
||||
};
|
||||
this.reset_internal = function() {
|
||||
return Problem.prototype.reset_internal.apply(that, arguments);
|
||||
};
|
||||
this.reset = function() {
|
||||
return Problem.prototype.reset.apply(that, arguments);
|
||||
};
|
||||
this.get_sr_status = function(contents) { // eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.get_sr_status.apply(that, arguments);
|
||||
};
|
||||
this.submit_internal = function() {
|
||||
return Problem.prototype.submit_internal.apply(that, arguments);
|
||||
};
|
||||
this.submit = function() {
|
||||
return Problem.prototype.submit.apply(that, arguments);
|
||||
};
|
||||
this.submit_fd = function() {
|
||||
return Problem.prototype.submit_fd.apply(that, arguments);
|
||||
};
|
||||
this.focus_on_save_notification = function() {
|
||||
return Problem.prototype.focus_on_save_notification.apply(that, arguments);
|
||||
};
|
||||
this.focus_on_hint_notification = function() {
|
||||
return Problem.prototype.focus_on_hint_notification.apply(that, arguments);
|
||||
};
|
||||
this.focus_on_submit_notification = function() {
|
||||
return Problem.prototype.focus_on_submit_notification.apply(that, arguments);
|
||||
};
|
||||
this.focus_on_notification = function(type) { // eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.focus_on_notification.apply(that, arguments);
|
||||
};
|
||||
this.scroll_to_problem_meta = function() {
|
||||
return Problem.prototype.scroll_to_problem_meta.apply(that, arguments);
|
||||
};
|
||||
this.submit_save_waitfor = function(callback) { // eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.submit_save_waitfor.apply(that, arguments);
|
||||
};
|
||||
this.setupInputTypes = function() {
|
||||
return Problem.prototype.setupInputTypes.apply(that, arguments);
|
||||
};
|
||||
this.poll = function(prevTimeout, focusCallback // eslint-disable-line no-unused-vars
|
||||
) {
|
||||
return Problem.prototype.poll.apply(that, arguments);
|
||||
};
|
||||
this.queueing = function(focusCallback) { // eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.queueing.apply(that, arguments);
|
||||
};
|
||||
this.forceUpdate = function(response) { // eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.forceUpdate.apply(that, arguments);
|
||||
};
|
||||
this.updateProgress = function(response) { // eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.updateProgress.apply(that, arguments);
|
||||
};
|
||||
this.renderProgressState = function() {
|
||||
return Problem.prototype.renderProgressState.apply(that, arguments);
|
||||
};
|
||||
this.bind = function() {
|
||||
return Problem.prototype.bind.apply(that, arguments);
|
||||
};
|
||||
this.el = $(element).find('.problems-wrapper');
|
||||
this.id = this.el.data('problem-id');
|
||||
this.element_id = this.el.attr('id');
|
||||
this.url = this.el.data('url');
|
||||
this.content = this.el.data('content');
|
||||
|
||||
// has_timed_out and has_response are used to ensure that
|
||||
// we wait a minimum of ~ 1s before transitioning the submit
|
||||
// button from disabled to enabled
|
||||
this.has_timed_out = false;
|
||||
this.has_response = false;
|
||||
this.render(this.content);
|
||||
var indexOfHelper =
|
||||
[].indexOf ||
|
||||
function (item) {
|
||||
var i, len;
|
||||
for (i = 0, len = this.length; i < len; i++) {
|
||||
if (i in this && this[i] === item) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
Problem.prototype.$ = function(selector) {
|
||||
return $(selector, this.el);
|
||||
};
|
||||
this.Problem = function () {
|
||||
function Problem(element) {
|
||||
var that = this;
|
||||
this.hint_button = function () {
|
||||
return Problem.prototype.hint_button.apply(that, arguments);
|
||||
};
|
||||
this.enableSubmitButtonAfterTimeout = function () {
|
||||
return Problem.prototype.enableSubmitButtonAfterTimeout.apply(that, arguments);
|
||||
};
|
||||
this.enableSubmitButtonAfterResponse = function () {
|
||||
return Problem.prototype.enableSubmitButtonAfterResponse.apply(that, arguments);
|
||||
};
|
||||
this.enableSubmitButton = function (enable, changeText) {
|
||||
if (changeText === null || changeText === undefined) {
|
||||
changeText = true; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
return Problem.prototype.enableSubmitButton.apply(that, arguments);
|
||||
};
|
||||
this.disableAllButtonsWhileRunning = function (
|
||||
operationCallback,
|
||||
isFromCheckOperation, // eslint-disable-line no-unused-vars
|
||||
) {
|
||||
return Problem.prototype.disableAllButtonsWhileRunning.apply(that, arguments);
|
||||
};
|
||||
this.submitAnswersAndSubmitButton = function (bind) {
|
||||
if (bind === null || bind === undefined) {
|
||||
bind = false; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
return Problem.prototype.submitAnswersAndSubmitButton.apply(that, arguments);
|
||||
};
|
||||
this.refreshAnswers = function () {
|
||||
return Problem.prototype.refreshAnswers.apply(that, arguments);
|
||||
};
|
||||
this.updateMathML = function (jax, el) {
|
||||
// eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.updateMathML.apply(that, arguments);
|
||||
};
|
||||
this.refreshMath = function (event, el) {
|
||||
// eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.refreshMath.apply(that, arguments);
|
||||
};
|
||||
this.save_internal = function () {
|
||||
return Problem.prototype.save_internal.apply(that, arguments);
|
||||
};
|
||||
this.save = function () {
|
||||
return Problem.prototype.save.apply(that, arguments);
|
||||
};
|
||||
this.gentle_alert = function (msg) {
|
||||
// eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.gentle_alert.apply(that, arguments);
|
||||
};
|
||||
this.clear_all_notifications = function () {
|
||||
return Problem.prototype.clear_all_notifications.apply(that, arguments);
|
||||
};
|
||||
this.show = function () {
|
||||
return Problem.prototype.show.apply(that, arguments);
|
||||
};
|
||||
this.reset_internal = function () {
|
||||
return Problem.prototype.reset_internal.apply(that, arguments);
|
||||
};
|
||||
this.reset = function () {
|
||||
return Problem.prototype.reset.apply(that, arguments);
|
||||
};
|
||||
this.get_sr_status = function (contents) {
|
||||
// eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.get_sr_status.apply(that, arguments);
|
||||
};
|
||||
this.submit_internal = function () {
|
||||
return Problem.prototype.submit_internal.apply(that, arguments);
|
||||
};
|
||||
this.submit = function () {
|
||||
return Problem.prototype.submit.apply(that, arguments);
|
||||
};
|
||||
this.submit_fd = function () {
|
||||
return Problem.prototype.submit_fd.apply(that, arguments);
|
||||
};
|
||||
this.focus_on_save_notification = function () {
|
||||
return Problem.prototype.focus_on_save_notification.apply(that, arguments);
|
||||
};
|
||||
this.focus_on_hint_notification = function () {
|
||||
return Problem.prototype.focus_on_hint_notification.apply(that, arguments);
|
||||
};
|
||||
this.focus_on_submit_notification = function () {
|
||||
return Problem.prototype.focus_on_submit_notification.apply(that, arguments);
|
||||
};
|
||||
this.focus_on_notification = function (type) {
|
||||
// eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.focus_on_notification.apply(that, arguments);
|
||||
};
|
||||
this.scroll_to_problem_meta = function () {
|
||||
return Problem.prototype.scroll_to_problem_meta.apply(that, arguments);
|
||||
};
|
||||
this.submit_save_waitfor = function (callback) {
|
||||
// eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.submit_save_waitfor.apply(that, arguments);
|
||||
};
|
||||
this.setupInputTypes = function () {
|
||||
return Problem.prototype.setupInputTypes.apply(that, arguments);
|
||||
};
|
||||
this.poll = function (
|
||||
prevTimeout,
|
||||
focusCallback, // eslint-disable-line no-unused-vars
|
||||
) {
|
||||
return Problem.prototype.poll.apply(that, arguments);
|
||||
};
|
||||
this.queueing = function (focusCallback) {
|
||||
// eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.queueing.apply(that, arguments);
|
||||
};
|
||||
this.forceUpdate = function (response) {
|
||||
// eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.forceUpdate.apply(that, arguments);
|
||||
};
|
||||
this.updateProgress = function (response) {
|
||||
// eslint-disable-line no-unused-vars
|
||||
return Problem.prototype.updateProgress.apply(that, arguments);
|
||||
};
|
||||
this.renderProgressState = function () {
|
||||
return Problem.prototype.renderProgressState.apply(that, arguments);
|
||||
};
|
||||
this.bind = function () {
|
||||
return Problem.prototype.bind.apply(that, arguments);
|
||||
};
|
||||
this.el = $(element).find(".problems-wrapper");
|
||||
this.id = this.el.data("problem-id");
|
||||
this.element_id = this.el.attr("id");
|
||||
this.url = this.el.data("url");
|
||||
this.content = this.el.data("content");
|
||||
|
||||
Problem.prototype.bind = function() {
|
||||
var problemPrefix,
|
||||
that = this;
|
||||
if (typeof MathJax !== 'undefined' && MathJax !== null) {
|
||||
this.el.find('.problem > div').each(function(index, element) {
|
||||
return MathJax.Hub.Queue(['Typeset', MathJax.Hub, element]);
|
||||
});
|
||||
}
|
||||
if (window.hasOwnProperty('update_schematics')) {
|
||||
window.update_schematics();
|
||||
}
|
||||
problemPrefix = this.element_id.replace(/problem_/, '');
|
||||
this.inputs = this.$('[id^="input_' + problemPrefix + '_"]');
|
||||
this.$('div.action button').click(this.refreshAnswers);
|
||||
this.reviewButton = this.$('.notification-btn.review-btn');
|
||||
this.reviewButton.click(this.scroll_to_problem_meta);
|
||||
this.submitButton = this.$('.action .submit');
|
||||
this.submitButtonLabel = this.$('.action .submit .submit-label');
|
||||
this.submitButtonSubmitText = this.submitButtonLabel.text();
|
||||
this.submitButtonSubmittingText = this.submitButton.data('submitting');
|
||||
this.submitButton.click(this.submit_fd);
|
||||
this.hintButton = this.$('.action .hint-button');
|
||||
this.hintButton.click(this.hint_button);
|
||||
this.resetButton = this.$('.action .reset');
|
||||
this.resetButton.click(this.reset);
|
||||
this.showButton = this.$('.action .show');
|
||||
this.showButton.click(this.show);
|
||||
this.saveButton = this.$('.action .save');
|
||||
this.saveNotification = this.$('.notification-save');
|
||||
this.showAnswerNotification = this.$('.notification-show-answer');
|
||||
this.saveButton.click(this.save);
|
||||
this.gentleAlertNotification = this.$('.notification-gentle-alert');
|
||||
this.submitNotification = this.$('.notification-submit');
|
||||
// has_timed_out and has_response are used to ensure that
|
||||
// we wait a minimum of ~ 1s before transitioning the submit
|
||||
// button from disabled to enabled
|
||||
this.has_timed_out = false;
|
||||
this.has_response = false;
|
||||
this.render(this.content);
|
||||
}
|
||||
|
||||
// Accessibility helper for sighted keyboard users to show <clarification> tooltips on focus:
|
||||
this.$('.clarification').focus(function(ev) {
|
||||
var icon;
|
||||
icon = $(ev.target).children('i');
|
||||
return window.globalTooltipManager.openTooltip(icon);
|
||||
Problem.prototype.$ = function (selector) {
|
||||
return $(selector, this.el);
|
||||
};
|
||||
|
||||
Problem.prototype.bind = function () {
|
||||
var problemPrefix,
|
||||
that = this;
|
||||
if (typeof MathJax !== "undefined" && MathJax !== null) {
|
||||
this.el.find(".problem > div").each(function (index, element) {
|
||||
return MathJax.Hub.Queue(["Typeset", MathJax.Hub, element]);
|
||||
});
|
||||
}
|
||||
if (window.hasOwnProperty("update_schematics")) {
|
||||
window.update_schematics();
|
||||
}
|
||||
problemPrefix = this.element_id.replace(/problem_/, "");
|
||||
this.inputs = this.$('[id^="input_' + problemPrefix + '_"]');
|
||||
this.$("div.action button").click(this.refreshAnswers);
|
||||
this.reviewButton = this.$(".notification-btn.review-btn");
|
||||
this.reviewButton.click(this.scroll_to_problem_meta);
|
||||
this.submitButton = this.$(".action .submit");
|
||||
this.submitButtonLabel = this.$(".action .submit .submit-label");
|
||||
this.submitButtonSubmitText = this.submitButtonLabel.text();
|
||||
this.submitButtonSubmittingText = this.submitButton.data("submitting");
|
||||
this.submitButton.click(this.submit_fd);
|
||||
this.hintButton = this.$(".action .hint-button");
|
||||
this.hintButton.click(this.hint_button);
|
||||
this.resetButton = this.$(".action .reset");
|
||||
this.resetButton.click(this.reset);
|
||||
this.showButton = this.$(".action .show");
|
||||
this.showButton.click(this.show);
|
||||
this.saveButton = this.$(".action .save");
|
||||
this.saveNotification = this.$(".notification-save");
|
||||
this.showAnswerNotification = this.$(".notification-show-answer");
|
||||
this.saveButton.click(this.save);
|
||||
this.gentleAlertNotification = this.$(".notification-gentle-alert");
|
||||
this.submitNotification = this.$(".notification-submit");
|
||||
|
||||
// Accessibility helper for sighted keyboard users to show <clarification> tooltips on focus:
|
||||
this.$(".clarification").focus(function (ev) {
|
||||
var icon;
|
||||
icon = $(ev.target).children("i");
|
||||
return window.globalTooltipManager.openTooltip(icon);
|
||||
});
|
||||
this.$(".clarification").blur(function () {
|
||||
return window.globalTooltipManager.hide();
|
||||
});
|
||||
this.$(".review-btn").focus(function (ev) {
|
||||
return $(ev.target).removeClass("sr");
|
||||
});
|
||||
this.$(".review-btn").blur(function (ev) {
|
||||
return $(ev.target).addClass("sr");
|
||||
});
|
||||
this.bindResetCorrectness();
|
||||
if (this.submitButton.length) {
|
||||
this.submitAnswersAndSubmitButton(true);
|
||||
}
|
||||
Collapsible.setCollapsibles(this.el);
|
||||
this.$("input.math").keyup(this.refreshMath);
|
||||
if (typeof MathJax !== "undefined" && MathJax !== null) {
|
||||
this.$("input.math").each(function (index, element) {
|
||||
return MathJax.Hub.Queue([that.refreshMath, null, element]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.renderProgressState = function () {
|
||||
var graded, progress, progressTemplate, curScore, totalScore, attemptsUsed;
|
||||
curScore = this.el.data("problem-score");
|
||||
totalScore = this.el.data("problem-total-possible");
|
||||
attemptsUsed = this.el.data("attempts-used");
|
||||
graded = this.el.data("graded");
|
||||
|
||||
// The problem is ungraded if it's explicitly marked as such, or if the total possible score is 0
|
||||
if (graded === "True" && totalScore !== 0) {
|
||||
graded = true;
|
||||
} else {
|
||||
graded = false;
|
||||
}
|
||||
|
||||
if (curScore === undefined || totalScore === undefined) {
|
||||
// Render an empty string.
|
||||
progressTemplate = "";
|
||||
} else if (curScore === null || curScore === "None") {
|
||||
// Render 'x point(s) possible (un/graded, results hidden)' if no current score provided.
|
||||
if (graded) {
|
||||
progressTemplate = ngettext(
|
||||
// Translators: {num_points} is the number of points possible (examples: 1, 3, 10).;
|
||||
"{num_points} point possible (graded, results hidden)",
|
||||
"{num_points} points possible (graded, results hidden)",
|
||||
totalScore,
|
||||
);
|
||||
} else {
|
||||
progressTemplate = ngettext(
|
||||
// Translators: {num_points} is the number of points possible (examples: 1, 3, 10).;
|
||||
"{num_points} point possible (ungraded, results hidden)",
|
||||
"{num_points} points possible (ungraded, results hidden)",
|
||||
totalScore,
|
||||
);
|
||||
}
|
||||
} else if ((attemptsUsed === 0 || totalScore === 0) && curScore === 0) {
|
||||
// Render 'x point(s) possible' if student has not yet attempted question
|
||||
// But if staff has overridden score to a non-zero number, show it
|
||||
if (graded) {
|
||||
progressTemplate = ngettext(
|
||||
// Translators: {num_points} is the number of points possible (examples: 1, 3, 10).;
|
||||
"{num_points} point possible (graded)",
|
||||
"{num_points} points possible (graded)",
|
||||
totalScore,
|
||||
);
|
||||
} else {
|
||||
progressTemplate = ngettext(
|
||||
// Translators: {num_points} is the number of points possible (examples: 1, 3, 10).;
|
||||
"{num_points} point possible (ungraded)",
|
||||
"{num_points} points possible (ungraded)",
|
||||
totalScore,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Render 'x/y point(s)' if student has attempted question
|
||||
if (graded) {
|
||||
progressTemplate = ngettext(
|
||||
// This comment needs to be on one line to be properly scraped for the translators.
|
||||
// Translators: {earned} is the number of points earned. {possible} is the total number of points (examples: 0/1, 1/1, 2/3, 5/10). The total number of points will always be at least 1. We pluralize based on the total number of points (example: 0/1 point; 1/2 points);
|
||||
"{earned}/{possible} point (graded)",
|
||||
"{earned}/{possible} points (graded)",
|
||||
totalScore,
|
||||
);
|
||||
} else {
|
||||
progressTemplate = ngettext(
|
||||
// This comment needs to be on one line to be properly scraped for the translators.
|
||||
// Translators: {earned} is the number of points earned. {possible} is the total number of points (examples: 0/1, 1/1, 2/3, 5/10). The total number of points will always be at least 1. We pluralize based on the total number of points (example: 0/1 point; 1/2 points);
|
||||
"{earned}/{possible} point (ungraded)",
|
||||
"{earned}/{possible} points (ungraded)",
|
||||
totalScore,
|
||||
);
|
||||
}
|
||||
}
|
||||
progress = edx.StringUtils.interpolate(progressTemplate, {
|
||||
earned: curScore,
|
||||
num_points: totalScore,
|
||||
possible: totalScore,
|
||||
});
|
||||
return this.$(".problem-progress").text(progress);
|
||||
};
|
||||
|
||||
Problem.prototype.updateProgress = function (response) {
|
||||
if (response.progress_changed) {
|
||||
this.el.data("problem-score", this.convertToFloat(response.current_score));
|
||||
this.el.data("problem-total-possible", this.convertToFloat(response.total_possible));
|
||||
this.el.data("attempts-used", response.attempts_used);
|
||||
this.el.trigger("progressChanged");
|
||||
}
|
||||
return this.renderProgressState();
|
||||
};
|
||||
|
||||
Problem.prototype.convertToFloat = function (num) {
|
||||
if (typeof num !== "number" || !Number.isInteger(num)) {
|
||||
return num;
|
||||
}
|
||||
return num.toFixed(1);
|
||||
};
|
||||
|
||||
Problem.prototype.forceUpdate = function (response) {
|
||||
this.el.data("problem-score", response.current_score);
|
||||
this.el.data("problem-total-possible", response.total_possible);
|
||||
this.el.data("attempts-used", response.attempts_used);
|
||||
this.el.trigger("progressChanged");
|
||||
return this.renderProgressState();
|
||||
};
|
||||
|
||||
Problem.prototype.queueing = function (focusCallback) {
|
||||
var that = this;
|
||||
this.queued_items = this.$(".xqueue");
|
||||
this.num_queued_items = this.queued_items.length;
|
||||
if (this.num_queued_items > 0) {
|
||||
if (window.queuePollerID) {
|
||||
// Only one poller 'thread' per Problem
|
||||
window.clearTimeout(window.queuePollerID);
|
||||
}
|
||||
window.queuePollerID = window.setTimeout(function () {
|
||||
return that.poll(1000, focusCallback);
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.poll = function (previousTimeout, focusCallback) {
|
||||
var that = this;
|
||||
return $.postWithPrefix("" + this.url + "/problem_get", function (response) {
|
||||
var newTimeout;
|
||||
// If queueing status changed, then render
|
||||
that.new_queued_items = $(response.html).find(".xqueue");
|
||||
if (that.new_queued_items.length !== that.num_queued_items) {
|
||||
edx.HtmlUtils.setHtml(that.el, edx.HtmlUtils.HTML(response.html))
|
||||
.promise()
|
||||
.done(function () {
|
||||
// eslint-disable-next-line no-void
|
||||
return typeof focusCallback === "function" ? focusCallback() : void 0;
|
||||
});
|
||||
this.$('.clarification').blur(function() {
|
||||
return window.globalTooltipManager.hide();
|
||||
});
|
||||
this.$('.review-btn').focus(function(ev) {
|
||||
return $(ev.target).removeClass('sr');
|
||||
});
|
||||
this.$('.review-btn').blur(function(ev) {
|
||||
return $(ev.target).addClass('sr');
|
||||
});
|
||||
this.bindResetCorrectness();
|
||||
if (this.submitButton.length) {
|
||||
this.submitAnswersAndSubmitButton(true);
|
||||
}
|
||||
Collapsible.setCollapsibles(this.el);
|
||||
this.$('input.math').keyup(this.refreshMath);
|
||||
if (typeof MathJax !== 'undefined' && MathJax !== null) {
|
||||
this.$('input.math').each(function(index, element) {
|
||||
return MathJax.Hub.Queue([that.refreshMath, null, element]);
|
||||
});
|
||||
}
|
||||
};
|
||||
JavascriptLoader.executeModuleScripts(that.el, function () {
|
||||
that.setupInputTypes();
|
||||
that.bind();
|
||||
});
|
||||
}
|
||||
that.num_queued_items = that.new_queued_items.length;
|
||||
if (that.num_queued_items === 0) {
|
||||
that.forceUpdate(response);
|
||||
delete window.queuePollerID;
|
||||
} else {
|
||||
newTimeout = previousTimeout * 2;
|
||||
// if the timeout is greather than 1 minute
|
||||
if (newTimeout >= 60000) {
|
||||
delete window.queuePollerID;
|
||||
that.gentle_alert(gettext("The grading process is still running. Refresh the page to see updates."));
|
||||
} else {
|
||||
window.queuePollerID = window.setTimeout(function () {
|
||||
return that.poll(newTimeout, focusCallback);
|
||||
}, newTimeout);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Problem.prototype.renderProgressState = function() {
|
||||
var graded, progress, progressTemplate, curScore, totalScore, attemptsUsed;
|
||||
curScore = this.el.data('problem-score');
|
||||
totalScore = this.el.data('problem-total-possible');
|
||||
attemptsUsed = this.el.data('attempts-used');
|
||||
graded = this.el.data('graded');
|
||||
/**
|
||||
* Use this if you want to make an ajax call on the input type object
|
||||
* static method so you don't have to instantiate a Problem in order to use it
|
||||
*
|
||||
* Input:
|
||||
* url: the AJAX url of the problem
|
||||
* inputId: the inputId of the input you would like to make the call on
|
||||
* NOTE: the id is the ${id} part of "input_${id}" during rendering
|
||||
* If this function is passed the entire prefixed id, the backend may have trouble
|
||||
* finding the correct input
|
||||
* dispatch: string that indicates how this data should be handled by the inputtype
|
||||
* data: dictionary of data to send to the server
|
||||
* callback: the function that will be called once the AJAX call has been completed.
|
||||
* It will be passed a response object
|
||||
*/
|
||||
Problem.inputAjax = function (url, inputId, dispatch, data, callback) {
|
||||
data.dispatch = dispatch; // eslint-disable-line no-param-reassign
|
||||
data.input_id = inputId; // eslint-disable-line no-param-reassign
|
||||
return $.postWithPrefix("" + url + "/input_ajax", data, callback);
|
||||
};
|
||||
|
||||
// The problem is ungraded if it's explicitly marked as such, or if the total possible score is 0
|
||||
if (graded === 'True' && totalScore !== 0) {
|
||||
graded = true;
|
||||
Problem.prototype.render = function (content, focusCallback) {
|
||||
var that = this;
|
||||
if (content) {
|
||||
edx.HtmlUtils.setHtml(this.el, edx.HtmlUtils.HTML(content));
|
||||
return JavascriptLoader.executeModuleScripts(this.el, function () {
|
||||
that.setupInputTypes();
|
||||
that.bind();
|
||||
that.queueing(focusCallback);
|
||||
that.renderProgressState();
|
||||
// eslint-disable-next-line no-void
|
||||
return typeof focusCallback === "function" ? focusCallback() : void 0;
|
||||
});
|
||||
} else {
|
||||
return $.postWithPrefix("" + this.url + "/problem_get", function (response) {
|
||||
edx.HtmlUtils.setHtml(that.el, edx.HtmlUtils.HTML(response.html));
|
||||
return JavascriptLoader.executeModuleScripts(that.el, function () {
|
||||
that.setupInputTypes();
|
||||
that.bind();
|
||||
that.queueing();
|
||||
return that.forceUpdate(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.setupInputTypes = function () {
|
||||
var that = this;
|
||||
this.inputtypeDisplays = {};
|
||||
return this.el.find(".capa_inputtype").each(function (index, inputtype) {
|
||||
var classes, cls, id, setupMethod, i, len, results;
|
||||
classes = $(inputtype).attr("class").split(" ");
|
||||
id = $(inputtype).attr("id");
|
||||
results = [];
|
||||
for (i = 0, len = classes.length; i < len; i++) {
|
||||
cls = classes[i];
|
||||
setupMethod = that.inputtypeSetupMethods[cls];
|
||||
if (setupMethod != null) {
|
||||
results.push((that.inputtypeDisplays[id] = setupMethod(inputtype)));
|
||||
} else {
|
||||
// eslint-disable-next-line no-void
|
||||
results.push(void 0);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* If some function wants to be called before sending the answer to the
|
||||
* server, give it a chance to do so.
|
||||
*
|
||||
* submit_save_waitfor allows the callee to send alerts if the user's input is
|
||||
* invalid. To do so, the callee must throw an exception named "WaitforException".
|
||||
* This and any other errors or exceptions that arise from the callee are rethrown
|
||||
* and abort the submission.
|
||||
*
|
||||
* In order to use this feature, add a 'data-waitfor' attribute to the input,
|
||||
* and specify the function to be called by the submit button before sending off @answers
|
||||
*/
|
||||
Problem.prototype.submit_save_waitfor = function (callback) {
|
||||
var flag,
|
||||
inp,
|
||||
i,
|
||||
len,
|
||||
ref,
|
||||
that = this;
|
||||
flag = false;
|
||||
ref = this.inputs;
|
||||
for (i = 0, len = ref.length; i < len; i++) {
|
||||
inp = ref[i];
|
||||
if ($(inp).is("input[waitfor]")) {
|
||||
try {
|
||||
$(inp).data("waitfor")(function () {
|
||||
that.refreshAnswers();
|
||||
return callback();
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.name === "Waitfor Exception") {
|
||||
alert(e.message); // eslint-disable-line no-alert
|
||||
} else {
|
||||
graded = false;
|
||||
alert(
|
||||
// eslint-disable-line no-alert
|
||||
gettext("Could not grade your answer. The submission was aborted."),
|
||||
);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
flag = true;
|
||||
} else {
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
};
|
||||
|
||||
if (curScore === undefined || totalScore === undefined) {
|
||||
// Render an empty string.
|
||||
progressTemplate = '';
|
||||
} else if (curScore === null || curScore === 'None') {
|
||||
// Render 'x point(s) possible (un/graded, results hidden)' if no current score provided.
|
||||
if (graded) {
|
||||
progressTemplate = ngettext(
|
||||
// Translators: {num_points} is the number of points possible (examples: 1, 3, 10).;
|
||||
'{num_points} point possible (graded, results hidden)',
|
||||
'{num_points} points possible (graded, results hidden)',
|
||||
totalScore
|
||||
);
|
||||
} else {
|
||||
progressTemplate = ngettext(
|
||||
// Translators: {num_points} is the number of points possible (examples: 1, 3, 10).;
|
||||
'{num_points} point possible (ungraded, results hidden)',
|
||||
'{num_points} points possible (ungraded, results hidden)',
|
||||
totalScore
|
||||
);
|
||||
}
|
||||
} else if ((attemptsUsed === 0 || totalScore === 0) && curScore === 0) {
|
||||
// Render 'x point(s) possible' if student has not yet attempted question
|
||||
// But if staff has overridden score to a non-zero number, show it
|
||||
if (graded) {
|
||||
progressTemplate = ngettext(
|
||||
// Translators: {num_points} is the number of points possible (examples: 1, 3, 10).;
|
||||
'{num_points} point possible (graded)', '{num_points} points possible (graded)',
|
||||
totalScore
|
||||
);
|
||||
} else {
|
||||
progressTemplate = ngettext(
|
||||
// Translators: {num_points} is the number of points possible (examples: 1, 3, 10).;
|
||||
'{num_points} point possible (ungraded)', '{num_points} points possible (ungraded)',
|
||||
totalScore
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Render 'x/y point(s)' if student has attempted question
|
||||
if (graded) {
|
||||
progressTemplate = ngettext(
|
||||
// This comment needs to be on one line to be properly scraped for the translators.
|
||||
// Translators: {earned} is the number of points earned. {possible} is the total number of points (examples: 0/1, 1/1, 2/3, 5/10). The total number of points will always be at least 1. We pluralize based on the total number of points (example: 0/1 point; 1/2 points);
|
||||
'{earned}/{possible} point (graded)', '{earned}/{possible} points (graded)',
|
||||
totalScore
|
||||
);
|
||||
} else {
|
||||
progressTemplate = ngettext(
|
||||
// This comment needs to be on one line to be properly scraped for the translators.
|
||||
// Translators: {earned} is the number of points earned. {possible} is the total number of points (examples: 0/1, 1/1, 2/3, 5/10). The total number of points will always be at least 1. We pluralize based on the total number of points (example: 0/1 point; 1/2 points);
|
||||
'{earned}/{possible} point (ungraded)', '{earned}/{possible} points (ungraded)',
|
||||
totalScore
|
||||
);
|
||||
}
|
||||
// Scroll to problem metadata and next focus is problem input
|
||||
Problem.prototype.scroll_to_problem_meta = function () {
|
||||
var questionTitle;
|
||||
questionTitle = this.$(".problem-header");
|
||||
if (questionTitle.length > 0) {
|
||||
$("html, body").animate(
|
||||
{
|
||||
scrollTop: questionTitle.offset().top,
|
||||
},
|
||||
500,
|
||||
);
|
||||
questionTitle.focus();
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.focus_on_notification = function (type) {
|
||||
var notification;
|
||||
notification = this.$(".notification-" + type);
|
||||
if (notification.length > 0) {
|
||||
notification.focus();
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.focus_on_submit_notification = function () {
|
||||
this.focus_on_notification("submit");
|
||||
};
|
||||
|
||||
Problem.prototype.focus_on_hint_notification = function (hintIndex) {
|
||||
this.$(".notification-hint .notification-message > ol > li.hint-index-" + hintIndex).focus();
|
||||
};
|
||||
|
||||
Problem.prototype.focus_on_save_notification = function () {
|
||||
this.focus_on_notification("save");
|
||||
};
|
||||
|
||||
/**
|
||||
* 'submit_fd' uses FormData to allow file submissions in the 'problem_check' dispatch,
|
||||
* in addition to simple querystring-based answers
|
||||
*
|
||||
* NOTE: The dispatch 'problem_check' is being singled out for the use of FormData;
|
||||
* maybe preferable to consolidate all dispatches to use FormData
|
||||
*/
|
||||
Problem.prototype.submit_fd = function () {
|
||||
var abortSubmission,
|
||||
error,
|
||||
errorHtml,
|
||||
errors,
|
||||
fd,
|
||||
fileNotSelected,
|
||||
fileTooLarge,
|
||||
maxFileSize,
|
||||
requiredFilesNotSubmitted,
|
||||
settings,
|
||||
timeoutId,
|
||||
unallowedFileSubmitted,
|
||||
i,
|
||||
len,
|
||||
that = this;
|
||||
|
||||
// If there are no file inputs in the problem, we can fall back on submit.
|
||||
if (this.el.find("input:file").length === 0) {
|
||||
this.submit();
|
||||
return;
|
||||
}
|
||||
this.enableSubmitButton(false);
|
||||
if (!window.FormData) {
|
||||
alert(
|
||||
gettext(
|
||||
"Submission aborted! Sorry, your browser does not support file uploads. If you can, please use Chrome or Safari which have been verified to support file uploads.",
|
||||
),
|
||||
); // eslint-disable-line max-len, no-alert
|
||||
this.enableSubmitButton(true);
|
||||
return;
|
||||
}
|
||||
timeoutId = this.enableSubmitButtonAfterTimeout();
|
||||
fd = new FormData();
|
||||
|
||||
// Sanity checks on submission
|
||||
maxFileSize = 4 * 1000 * 1000;
|
||||
fileTooLarge = false;
|
||||
fileNotSelected = false;
|
||||
requiredFilesNotSubmitted = false;
|
||||
unallowedFileSubmitted = false;
|
||||
|
||||
errors = [];
|
||||
this.inputs.each(function (index, element) {
|
||||
var allowedFiles, file, maxSize, requiredFiles, loopI, loopLen, ref;
|
||||
if (element.type === "file") {
|
||||
requiredFiles = $(element).data("required_files");
|
||||
allowedFiles = $(element).data("allowed_files");
|
||||
ref = element.files;
|
||||
for (loopI = 0, loopLen = ref.length; loopI < loopLen; loopI++) {
|
||||
file = ref[loopI];
|
||||
if (allowedFiles.length !== 0 && indexOfHelper.call(allowedFiles, file.name) < 0) {
|
||||
unallowedFileSubmitted = true;
|
||||
errors.push(
|
||||
edx.StringUtils.interpolate(gettext("You submitted {filename}; only {allowedFiles} are allowed."), {
|
||||
filename: file.name,
|
||||
allowedFiles: allowedFiles,
|
||||
}),
|
||||
);
|
||||
}
|
||||
progress = edx.StringUtils.interpolate(
|
||||
progressTemplate, {
|
||||
earned: curScore,
|
||||
num_points: totalScore,
|
||||
possible: totalScore
|
||||
}
|
||||
if (indexOfHelper.call(requiredFiles, file.name) >= 0) {
|
||||
requiredFiles.splice(requiredFiles.indexOf(file.name), 1);
|
||||
}
|
||||
if (file.size > maxFileSize) {
|
||||
fileTooLarge = true;
|
||||
maxSize = maxFileSize / (1000 * 1000);
|
||||
errors.push(
|
||||
edx.StringUtils.interpolate(gettext("Your file {filename} is too large (max size: {maxSize}MB)."), {
|
||||
filename: file.name,
|
||||
maxSize: maxSize,
|
||||
}),
|
||||
);
|
||||
}
|
||||
fd.append(element.id, file); // xss-lint: disable=javascript-jquery-append
|
||||
}
|
||||
if (element.files.length === 0) {
|
||||
fileNotSelected = true;
|
||||
// In case we want to allow submissions with no file
|
||||
fd.append(element.id, ""); // xss-lint: disable=javascript-jquery-append
|
||||
}
|
||||
if (requiredFiles.length !== 0) {
|
||||
requiredFilesNotSubmitted = true;
|
||||
errors.push(
|
||||
edx.StringUtils.interpolate(gettext("You did not submit the required files: {requiredFiles}."), {
|
||||
requiredFiles: requiredFiles,
|
||||
}),
|
||||
);
|
||||
return this.$('.problem-progress').text(progress);
|
||||
};
|
||||
|
||||
Problem.prototype.updateProgress = function(response) {
|
||||
if (response.progress_changed) {
|
||||
this.el.data('problem-score', this.convertToFloat(response.current_score));
|
||||
this.el.data('problem-total-possible', this.convertToFloat(response.total_possible));
|
||||
this.el.data('attempts-used', response.attempts_used);
|
||||
this.el.trigger('progressChanged');
|
||||
}
|
||||
return this.renderProgressState();
|
||||
};
|
||||
|
||||
Problem.prototype.convertToFloat = function(num) {
|
||||
if (typeof num !== 'number' || !Number.isInteger(num)) {
|
||||
return num;
|
||||
}
|
||||
return num.toFixed(1);
|
||||
};
|
||||
|
||||
Problem.prototype.forceUpdate = function(response) {
|
||||
this.el.data('problem-score', response.current_score);
|
||||
this.el.data('problem-total-possible', response.total_possible);
|
||||
this.el.data('attempts-used', response.attempts_used);
|
||||
this.el.trigger('progressChanged');
|
||||
return this.renderProgressState();
|
||||
};
|
||||
|
||||
Problem.prototype.queueing = function(focusCallback) {
|
||||
var that = this;
|
||||
this.queued_items = this.$('.xqueue');
|
||||
this.num_queued_items = this.queued_items.length;
|
||||
if (this.num_queued_items > 0) {
|
||||
if (window.queuePollerID) { // Only one poller 'thread' per Problem
|
||||
window.clearTimeout(window.queuePollerID);
|
||||
}
|
||||
window.queuePollerID = window.setTimeout(function() {
|
||||
return that.poll(1000, focusCallback);
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.poll = function(previousTimeout, focusCallback) {
|
||||
var that = this;
|
||||
return $.postWithPrefix('' + this.url + '/problem_get', function(response) {
|
||||
var newTimeout;
|
||||
// If queueing status changed, then render
|
||||
that.new_queued_items = $(response.html).find('.xqueue');
|
||||
if (that.new_queued_items.length !== that.num_queued_items) {
|
||||
edx.HtmlUtils.setHtml(that.el, edx.HtmlUtils.HTML(response.html)).promise().done(function() {
|
||||
// eslint-disable-next-line no-void
|
||||
return typeof focusCallback === 'function' ? focusCallback() : void 0;
|
||||
});
|
||||
JavascriptLoader.executeModuleScripts(that.el, function() {
|
||||
that.setupInputTypes();
|
||||
that.bind();
|
||||
});
|
||||
}
|
||||
that.num_queued_items = that.new_queued_items.length;
|
||||
if (that.num_queued_items === 0) {
|
||||
that.forceUpdate(response);
|
||||
delete window.queuePollerID;
|
||||
} else {
|
||||
newTimeout = previousTimeout * 2;
|
||||
// if the timeout is greather than 1 minute
|
||||
if (newTimeout >= 60000) {
|
||||
delete window.queuePollerID;
|
||||
that.gentle_alert(
|
||||
gettext('The grading process is still running. Refresh the page to see updates.')
|
||||
);
|
||||
} else {
|
||||
window.queuePollerID = window.setTimeout(function() {
|
||||
return that.poll(newTimeout, focusCallback);
|
||||
}, newTimeout);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Use this if you want to make an ajax call on the input type object
|
||||
* static method so you don't have to instantiate a Problem in order to use it
|
||||
*
|
||||
* Input:
|
||||
* url: the AJAX url of the problem
|
||||
* inputId: the inputId of the input you would like to make the call on
|
||||
* NOTE: the id is the ${id} part of "input_${id}" during rendering
|
||||
* If this function is passed the entire prefixed id, the backend may have trouble
|
||||
* finding the correct input
|
||||
* dispatch: string that indicates how this data should be handled by the inputtype
|
||||
* data: dictionary of data to send to the server
|
||||
* callback: the function that will be called once the AJAX call has been completed.
|
||||
* It will be passed a response object
|
||||
*/
|
||||
Problem.inputAjax = function(url, inputId, dispatch, data, callback) {
|
||||
data.dispatch = dispatch; // eslint-disable-line no-param-reassign
|
||||
data.input_id = inputId; // eslint-disable-line no-param-reassign
|
||||
return $.postWithPrefix('' + url + '/input_ajax', data, callback);
|
||||
};
|
||||
|
||||
Problem.prototype.render = function(content, focusCallback) {
|
||||
var that = this;
|
||||
if (content) {
|
||||
edx.HtmlUtils.setHtml(this.el, edx.HtmlUtils.HTML(content));
|
||||
return JavascriptLoader.executeModuleScripts(this.el, function() {
|
||||
that.setupInputTypes();
|
||||
that.bind();
|
||||
that.queueing(focusCallback);
|
||||
that.renderProgressState();
|
||||
// eslint-disable-next-line no-void
|
||||
return typeof focusCallback === 'function' ? focusCallback() : void 0;
|
||||
});
|
||||
} else {
|
||||
return $.postWithPrefix('' + this.url + '/problem_get', function(response) {
|
||||
edx.HtmlUtils.setHtml(that.el, edx.HtmlUtils.HTML(response.html));
|
||||
return JavascriptLoader.executeModuleScripts(that.el, function() {
|
||||
that.setupInputTypes();
|
||||
that.bind();
|
||||
that.queueing();
|
||||
return that.forceUpdate(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.setupInputTypes = function() {
|
||||
var that = this;
|
||||
this.inputtypeDisplays = {};
|
||||
return this.el.find('.capa_inputtype').each(function(index, inputtype) {
|
||||
var classes, cls, id, setupMethod, i, len, results;
|
||||
classes = $(inputtype).attr('class').split(' ');
|
||||
id = $(inputtype).attr('id');
|
||||
results = [];
|
||||
for (i = 0, len = classes.length; i < len; i++) {
|
||||
cls = classes[i];
|
||||
setupMethod = that.inputtypeSetupMethods[cls];
|
||||
if (setupMethod != null) {
|
||||
results.push(that.inputtypeDisplays[id] = setupMethod(inputtype));
|
||||
} else {
|
||||
// eslint-disable-next-line no-void
|
||||
results.push(void 0);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* If some function wants to be called before sending the answer to the
|
||||
* server, give it a chance to do so.
|
||||
*
|
||||
* submit_save_waitfor allows the callee to send alerts if the user's input is
|
||||
* invalid. To do so, the callee must throw an exception named "WaitforException".
|
||||
* This and any other errors or exceptions that arise from the callee are rethrown
|
||||
* and abort the submission.
|
||||
*
|
||||
* In order to use this feature, add a 'data-waitfor' attribute to the input,
|
||||
* and specify the function to be called by the submit button before sending off @answers
|
||||
*/
|
||||
Problem.prototype.submit_save_waitfor = function(callback) {
|
||||
var flag, inp, i, len, ref,
|
||||
that = this;
|
||||
flag = false;
|
||||
ref = this.inputs;
|
||||
for (i = 0, len = ref.length; i < len; i++) {
|
||||
inp = ref[i];
|
||||
if ($(inp).is('input[waitfor]')) {
|
||||
try {
|
||||
$(inp).data('waitfor')(function() {
|
||||
that.refreshAnswers();
|
||||
return callback();
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.name === 'Waitfor Exception') {
|
||||
alert(e.message); // eslint-disable-line no-alert
|
||||
} else {
|
||||
alert( // eslint-disable-line no-alert
|
||||
gettext('Could not grade your answer. The submission was aborted.')
|
||||
);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
flag = true;
|
||||
} else {
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
};
|
||||
|
||||
// Scroll to problem metadata and next focus is problem input
|
||||
Problem.prototype.scroll_to_problem_meta = function() {
|
||||
var questionTitle;
|
||||
questionTitle = this.$('.problem-header');
|
||||
if (questionTitle.length > 0) {
|
||||
$('html, body').animate({
|
||||
scrollTop: questionTitle.offset().top
|
||||
}, 500);
|
||||
questionTitle.focus();
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.focus_on_notification = function(type) {
|
||||
var notification;
|
||||
notification = this.$('.notification-' + type);
|
||||
if (notification.length > 0) {
|
||||
notification.focus();
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.focus_on_submit_notification = function() {
|
||||
this.focus_on_notification('submit');
|
||||
};
|
||||
|
||||
Problem.prototype.focus_on_hint_notification = function(hintIndex) {
|
||||
this.$('.notification-hint .notification-message > ol > li.hint-index-' + hintIndex).focus();
|
||||
};
|
||||
|
||||
Problem.prototype.focus_on_save_notification = function() {
|
||||
this.focus_on_notification('save');
|
||||
};
|
||||
|
||||
/**
|
||||
* 'submit_fd' uses FormData to allow file submissions in the 'problem_check' dispatch,
|
||||
* in addition to simple querystring-based answers
|
||||
*
|
||||
* NOTE: The dispatch 'problem_check' is being singled out for the use of FormData;
|
||||
* maybe preferable to consolidate all dispatches to use FormData
|
||||
*/
|
||||
Problem.prototype.submit_fd = function() {
|
||||
var abortSubmission, error, errorHtml, errors, fd, fileNotSelected, fileTooLarge, maxFileSize,
|
||||
requiredFilesNotSubmitted, settings, timeoutId, unallowedFileSubmitted, i, len,
|
||||
that = this;
|
||||
|
||||
// If there are no file inputs in the problem, we can fall back on submit.
|
||||
if (this.el.find('input:file').length === 0) {
|
||||
this.submit();
|
||||
return;
|
||||
}
|
||||
this.enableSubmitButton(false);
|
||||
if (!window.FormData) {
|
||||
alert(gettext('Submission aborted! Sorry, your browser does not support file uploads. If you can, please use Chrome or Safari which have been verified to support file uploads.')); // eslint-disable-line max-len, no-alert
|
||||
this.enableSubmitButton(true);
|
||||
return;
|
||||
}
|
||||
timeoutId = this.enableSubmitButtonAfterTimeout();
|
||||
fd = new FormData();
|
||||
|
||||
// Sanity checks on submission
|
||||
maxFileSize = 4 * 1000 * 1000;
|
||||
fileTooLarge = false;
|
||||
fileNotSelected = false;
|
||||
requiredFilesNotSubmitted = false;
|
||||
unallowedFileSubmitted = false;
|
||||
|
||||
errors = [];
|
||||
this.inputs.each(function(index, element) {
|
||||
var allowedFiles, file, maxSize, requiredFiles, loopI, loopLen, ref;
|
||||
if (element.type === 'file') {
|
||||
requiredFiles = $(element).data('required_files');
|
||||
allowedFiles = $(element).data('allowed_files');
|
||||
ref = element.files;
|
||||
for (loopI = 0, loopLen = ref.length; loopI < loopLen; loopI++) {
|
||||
file = ref[loopI];
|
||||
if (allowedFiles.length !== 0 && indexOfHelper.call(allowedFiles, file.name) < 0) {
|
||||
unallowedFileSubmitted = true;
|
||||
errors.push(edx.StringUtils.interpolate(
|
||||
gettext('You submitted {filename}; only {allowedFiles} are allowed.'), {
|
||||
filename: file.name,
|
||||
allowedFiles: allowedFiles
|
||||
}
|
||||
));
|
||||
}
|
||||
if (indexOfHelper.call(requiredFiles, file.name) >= 0) {
|
||||
requiredFiles.splice(requiredFiles.indexOf(file.name), 1);
|
||||
}
|
||||
if (file.size > maxFileSize) {
|
||||
fileTooLarge = true;
|
||||
maxSize = maxFileSize / (1000 * 1000);
|
||||
errors.push(edx.StringUtils.interpolate(
|
||||
gettext('Your file {filename} is too large (max size: {maxSize}MB).'), {
|
||||
filename: file.name,
|
||||
maxSize: maxSize
|
||||
}
|
||||
));
|
||||
}
|
||||
fd.append(element.id, file); // xss-lint: disable=javascript-jquery-append
|
||||
}
|
||||
if (element.files.length === 0) {
|
||||
fileNotSelected = true;
|
||||
// In case we want to allow submissions with no file
|
||||
fd.append(element.id, ''); // xss-lint: disable=javascript-jquery-append
|
||||
}
|
||||
if (requiredFiles.length !== 0) {
|
||||
requiredFilesNotSubmitted = true;
|
||||
errors.push(edx.StringUtils.interpolate(
|
||||
gettext('You did not submit the required files: {requiredFiles}.'), {
|
||||
requiredFiles: requiredFiles
|
||||
}
|
||||
));
|
||||
}
|
||||
} else {
|
||||
fd.append(element.id, element.value); // xss-lint: disable=javascript-jquery-append
|
||||
}
|
||||
});
|
||||
if (fileNotSelected) {
|
||||
errors.push(gettext('You did not select any files to submit.'));
|
||||
}
|
||||
errorHtml = '';
|
||||
for (i = 0, len = errors.length; i < len; i++) {
|
||||
error = errors[i];
|
||||
errorHtml = edx.HtmlUtils.joinHtml(
|
||||
errorHtml,
|
||||
edx.HtmlUtils.interpolateHtml(edx.HtmlUtils.HTML('<li>{error}</li>'), {error: error})
|
||||
);
|
||||
}
|
||||
errorHtml = edx.HtmlUtils.interpolateHtml(edx.HtmlUtils.HTML('<ul>{errors}</ul>'), {errors: errorHtml});
|
||||
this.gentle_alert(errorHtml.toString());
|
||||
abortSubmission = fileTooLarge || fileNotSelected || unallowedFileSubmitted || requiredFilesNotSubmitted;
|
||||
if (abortSubmission) {
|
||||
window.clearTimeout(timeoutId);
|
||||
this.enableSubmitButton(true);
|
||||
} else {
|
||||
settings = {
|
||||
type: 'POST',
|
||||
data: fd,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
complete: this.enableSubmitButtonAfterResponse,
|
||||
success: function(response) {
|
||||
switch (response.success) {
|
||||
case 'submitted':
|
||||
case 'incorrect':
|
||||
case 'correct':
|
||||
that.render(response.contents);
|
||||
that.updateProgress(response);
|
||||
break;
|
||||
default:
|
||||
that.gentle_alert(response.success);
|
||||
}
|
||||
return Logger.log('problem_graded', [that.answers, response.contents], that.id);
|
||||
},
|
||||
error: function(response) {
|
||||
that.gentle_alert(response.responseJSON.success);
|
||||
}
|
||||
};
|
||||
$.ajaxWithPrefix('' + this.url + '/problem_check', settings);
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.submit = function() {
|
||||
if (!this.submit_save_waitfor(this.submit_internal)) {
|
||||
this.disableAllButtonsWhileRunning(this.submit_internal, true);
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.submit_internal = function() {
|
||||
var that = this;
|
||||
Logger.log('problem_check', this.answers);
|
||||
return $.postWithPrefix('' + this.url + '/problem_check', this.answers, function(response) {
|
||||
switch (response.success) {
|
||||
case 'submitted':
|
||||
case 'incorrect':
|
||||
case 'correct':
|
||||
window.SR.readTexts(that.get_sr_status(response.contents));
|
||||
that.el.trigger('contentChanged', [that.id, response.contents, response]);
|
||||
that.render(response.contents, that.focus_on_submit_notification);
|
||||
that.updateProgress(response);
|
||||
// This is used by the Learning MFE to know when the Entrance Exam has been passed
|
||||
// for a user. The MFE is then able to respond appropriately.
|
||||
if (response.entrance_exam_passed) {
|
||||
window.parent.postMessage({type: 'entranceExam.passed'}, '*');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
that.saveNotification.hide();
|
||||
that.gentle_alert(response.success);
|
||||
}
|
||||
return Logger.log('problem_graded', [that.answers, response.contents], that.id);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This method builds up an array of strings to send to the page screen-reader span.
|
||||
* It first gets all elements with class "status", and then looks to see if they are contained
|
||||
* in sections with aria-labels. If so, labels are prepended to the status element text.
|
||||
* If not, just the text of the status elements are returned.
|
||||
*/
|
||||
Problem.prototype.get_sr_status = function(contents) {
|
||||
var addedStatus, ariaLabel, element, labeledStatus, parentSection, statusElement, template, i, len;
|
||||
statusElement = $(contents).find('.status');
|
||||
labeledStatus = [];
|
||||
for (i = 0, len = statusElement.length; i < len; i++) {
|
||||
element = statusElement[i];
|
||||
parentSection = $(element).closest('.wrapper-problem-response');
|
||||
addedStatus = false;
|
||||
if (parentSection) {
|
||||
ariaLabel = parentSection.attr('aria-label');
|
||||
if (ariaLabel) {
|
||||
// Translators: This is only translated to allow for reordering of label and associated status.;
|
||||
template = gettext('{label}: {status}');
|
||||
labeledStatus.push(edx.StringUtils.interpolate(
|
||||
template, {
|
||||
label: ariaLabel,
|
||||
status: $(element).text()
|
||||
}
|
||||
));
|
||||
addedStatus = true;
|
||||
}
|
||||
}
|
||||
if (!addedStatus) {
|
||||
labeledStatus.push($(element).text());
|
||||
}
|
||||
}
|
||||
return labeledStatus;
|
||||
};
|
||||
|
||||
Problem.prototype.reset = function() {
|
||||
return this.disableAllButtonsWhileRunning(this.reset_internal, false);
|
||||
};
|
||||
|
||||
Problem.prototype.reset_internal = function() {
|
||||
var that = this;
|
||||
Logger.log('problem_reset', this.answers);
|
||||
return $.postWithPrefix('' + this.url + '/problem_reset', {
|
||||
id: this.id
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
that.el.trigger('contentChanged', [that.id, response.html, response]);
|
||||
that.render(response.html, that.scroll_to_problem_meta);
|
||||
that.updateProgress(response);
|
||||
return window.SR.readText(gettext('This problem has been reset.'));
|
||||
} else {
|
||||
return that.gentle_alert(response.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// TODO this needs modification to deal with javascript responses; perhaps we
|
||||
// need something where responsetypes can define their own behavior when show
|
||||
// is called.
|
||||
Problem.prototype.show = function() {
|
||||
var that = this;
|
||||
Logger.log('problem_show', {
|
||||
problem: this.id
|
||||
});
|
||||
return $.postWithPrefix('' + this.url + '/problem_show', function(response) {
|
||||
var answers;
|
||||
answers = response.answers;
|
||||
$.each(answers, function(key, value) {
|
||||
var safeKey = key.replace(':', '\\:'); // fix for courses which use url_names with colons, e.g. problem:question1
|
||||
safeKey = safeKey.replace(/\./g, '\\.'); // fix for courses which use url_names with periods. e.g. question1.1
|
||||
var answer;
|
||||
if (!$.isArray(value)) {
|
||||
answer = that.$('#answer_' + safeKey + ', #solution_' + safeKey);
|
||||
edx.HtmlUtils.setHtml(answer, edx.HtmlUtils.HTML(value));
|
||||
Collapsible.setCollapsibles(answer);
|
||||
|
||||
// Sometimes, `value` is just a string containing a MathJax formula.
|
||||
// If this is the case, jQuery will throw an error in some corner cases
|
||||
// because of an incorrect selector. We setup a try..catch so that
|
||||
// the script doesn't break in such cases.
|
||||
//
|
||||
// We will fallback to the second `if statement` below, if an
|
||||
// error is thrown by jQuery.
|
||||
try {
|
||||
return $(value).find('.detailed-solution');
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO remove the above once everything is extracted into its own
|
||||
// inputtype functions.
|
||||
}
|
||||
});
|
||||
that.el.find('.capa_inputtype').each(function(index, inputtype) {
|
||||
var classes, cls, display, showMethod, i, len, results;
|
||||
classes = $(inputtype).attr('class').split(' ');
|
||||
results = [];
|
||||
for (i = 0, len = classes.length; i < len; i++) {
|
||||
cls = classes[i];
|
||||
display = that.inputtypeDisplays[$(inputtype).attr('id')];
|
||||
showMethod = that.inputtypeShowAnswerMethods[cls];
|
||||
if (showMethod != null) {
|
||||
results.push(showMethod(inputtype, display, answers, response.correct_status_html));
|
||||
} else {
|
||||
// eslint-disable-next-line no-void
|
||||
results.push(void 0);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
});
|
||||
if (typeof MathJax !== 'undefined' && MathJax !== null) {
|
||||
that.el.find('.problem > div').each(function(index, element) {
|
||||
return MathJax.Hub.Queue(['Typeset', MathJax.Hub, element]);
|
||||
});
|
||||
}
|
||||
that.el.find('.show').attr('disabled', 'disabled');
|
||||
}
|
||||
} else {
|
||||
fd.append(element.id, element.value); // xss-lint: disable=javascript-jquery-append
|
||||
}
|
||||
});
|
||||
if (fileNotSelected) {
|
||||
errors.push(gettext("You did not select any files to submit."));
|
||||
}
|
||||
errorHtml = "";
|
||||
for (i = 0, len = errors.length; i < len; i++) {
|
||||
error = errors[i];
|
||||
errorHtml = edx.HtmlUtils.joinHtml(
|
||||
errorHtml,
|
||||
edx.HtmlUtils.interpolateHtml(edx.HtmlUtils.HTML("<li>{error}</li>"), { error: error }),
|
||||
);
|
||||
}
|
||||
errorHtml = edx.HtmlUtils.interpolateHtml(edx.HtmlUtils.HTML("<ul>{errors}</ul>"), { errors: errorHtml });
|
||||
this.gentle_alert(errorHtml.toString());
|
||||
abortSubmission = fileTooLarge || fileNotSelected || unallowedFileSubmitted || requiredFilesNotSubmitted;
|
||||
if (abortSubmission) {
|
||||
window.clearTimeout(timeoutId);
|
||||
this.enableSubmitButton(true);
|
||||
} else {
|
||||
settings = {
|
||||
type: "POST",
|
||||
data: fd,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
complete: this.enableSubmitButtonAfterResponse,
|
||||
success: function (response) {
|
||||
switch (response.success) {
|
||||
case "submitted":
|
||||
case "incorrect":
|
||||
case "correct":
|
||||
that.render(response.contents);
|
||||
that.updateProgress(response);
|
||||
that.clear_all_notifications();
|
||||
that.showAnswerNotification.show();
|
||||
that.focus_on_notification('show-answer');
|
||||
});
|
||||
break;
|
||||
default:
|
||||
that.gentle_alert(response.success);
|
||||
}
|
||||
return Logger.log("problem_graded", [that.answers, response.contents], that.id);
|
||||
},
|
||||
error: function (response) {
|
||||
that.gentle_alert(response.responseJSON.success);
|
||||
},
|
||||
};
|
||||
$.ajaxWithPrefix("" + this.url + "/problem_check", settings);
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.clear_all_notifications = function() {
|
||||
this.submitNotification.remove();
|
||||
this.gentleAlertNotification.hide();
|
||||
this.saveNotification.hide();
|
||||
this.showAnswerNotification.hide();
|
||||
};
|
||||
Problem.prototype.submit = function () {
|
||||
if (!this.submit_save_waitfor(this.submit_internal)) {
|
||||
this.disableAllButtonsWhileRunning(this.submit_internal, true);
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.gentle_alert = function(msg) {
|
||||
edx.HtmlUtils.setHtml(
|
||||
this.el.find('.notification-gentle-alert .notification-message'),
|
||||
edx.HtmlUtils.HTML(msg)
|
||||
Problem.prototype.submit_internal = function () {
|
||||
var that = this;
|
||||
Logger.log("problem_check", this.answers);
|
||||
return $.postWithPrefix("" + this.url + "/problem_check", this.answers, function (response) {
|
||||
switch (response.success) {
|
||||
case "submitted":
|
||||
case "incorrect":
|
||||
case "correct":
|
||||
window.SR.readTexts(that.get_sr_status(response.contents));
|
||||
that.el.trigger("contentChanged", [that.id, response.contents, response]);
|
||||
that.render(response.contents, that.focus_on_submit_notification);
|
||||
that.updateProgress(response);
|
||||
// This is used by the Learning MFE to know when the Entrance Exam has been passed
|
||||
// for a user. The MFE is then able to respond appropriately.
|
||||
if (response.entrance_exam_passed) {
|
||||
window.parent.postMessage({ type: "entranceExam.passed" }, "*");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
that.saveNotification.hide();
|
||||
that.gentle_alert(response.success);
|
||||
}
|
||||
return Logger.log("problem_graded", [that.answers, response.contents], that.id);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This method builds up an array of strings to send to the page screen-reader span.
|
||||
* It first gets all elements with class "status", and then looks to see if they are contained
|
||||
* in sections with aria-labels. If so, labels are prepended to the status element text.
|
||||
* If not, just the text of the status elements are returned.
|
||||
*/
|
||||
Problem.prototype.get_sr_status = function (contents) {
|
||||
var addedStatus, ariaLabel, element, labeledStatus, parentSection, statusElement, template, i, len;
|
||||
statusElement = $(contents).find(".status");
|
||||
labeledStatus = [];
|
||||
for (i = 0, len = statusElement.length; i < len; i++) {
|
||||
element = statusElement[i];
|
||||
parentSection = $(element).closest(".wrapper-problem-response");
|
||||
addedStatus = false;
|
||||
if (parentSection) {
|
||||
ariaLabel = parentSection.attr("aria-label");
|
||||
if (ariaLabel) {
|
||||
// Translators: This is only translated to allow for reordering of label and associated status.;
|
||||
template = gettext("{label}: {status}");
|
||||
labeledStatus.push(
|
||||
edx.StringUtils.interpolate(template, {
|
||||
label: ariaLabel,
|
||||
status: $(element).text(),
|
||||
}),
|
||||
);
|
||||
this.clear_all_notifications();
|
||||
this.gentleAlertNotification.show();
|
||||
this.gentleAlertNotification.focus();
|
||||
};
|
||||
addedStatus = true;
|
||||
}
|
||||
}
|
||||
if (!addedStatus) {
|
||||
labeledStatus.push($(element).text());
|
||||
}
|
||||
}
|
||||
return labeledStatus;
|
||||
};
|
||||
|
||||
Problem.prototype.save = function() {
|
||||
if (!this.submit_save_waitfor(this.save_internal)) {
|
||||
this.disableAllButtonsWhileRunning(this.save_internal, false);
|
||||
}
|
||||
};
|
||||
Problem.prototype.reset = function () {
|
||||
return this.disableAllButtonsWhileRunning(this.reset_internal, false);
|
||||
};
|
||||
|
||||
Problem.prototype.save_internal = function() {
|
||||
var that = this;
|
||||
Logger.log('problem_save', this.answers);
|
||||
return $.postWithPrefix('' + this.url + '/problem_save', this.answers, function(response) {
|
||||
var saveMessage;
|
||||
saveMessage = response.msg;
|
||||
if (response.success) {
|
||||
that.el.trigger('contentChanged', [that.id, response.html, response]);
|
||||
edx.HtmlUtils.setHtml(
|
||||
that.el.find('.notification-save .notification-message'),
|
||||
edx.HtmlUtils.HTML(saveMessage)
|
||||
);
|
||||
that.clear_all_notifications();
|
||||
that.el.find('.wrapper-problem-response .message').hide();
|
||||
that.saveNotification.show();
|
||||
that.focus_on_save_notification();
|
||||
} else {
|
||||
that.gentle_alert(saveMessage);
|
||||
}
|
||||
});
|
||||
};
|
||||
Problem.prototype.reset_internal = function () {
|
||||
var that = this;
|
||||
Logger.log("problem_reset", this.answers);
|
||||
return $.postWithPrefix(
|
||||
"" + this.url + "/problem_reset",
|
||||
{
|
||||
id: this.id,
|
||||
},
|
||||
function (response) {
|
||||
if (response.success) {
|
||||
that.el.trigger("contentChanged", [that.id, response.html, response]);
|
||||
that.render(response.html, that.scroll_to_problem_meta);
|
||||
that.updateProgress(response);
|
||||
return window.SR.readText(gettext("This problem has been reset."));
|
||||
} else {
|
||||
return that.gentle_alert(response.msg);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
Problem.prototype.refreshMath = function(event, element) {
|
||||
var elid, eqn, jax, mathjaxPreprocessor, preprocessorTag, target;
|
||||
if (!element) {
|
||||
element = event.target; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
elid = element.id.replace(/^input_/, '');
|
||||
target = 'display_' + elid;
|
||||
// TODO this needs modification to deal with javascript responses; perhaps we
|
||||
// need something where responsetypes can define their own behavior when show
|
||||
// is called.
|
||||
Problem.prototype.show = function () {
|
||||
var that = this;
|
||||
Logger.log("problem_show", {
|
||||
problem: this.id,
|
||||
});
|
||||
return $.postWithPrefix("" + this.url + "/problem_show", function (response) {
|
||||
var answers;
|
||||
answers = response.answers;
|
||||
$.each(answers, function (key, value) {
|
||||
var safeKey = key.replace(/\\/g, "\\\\").replace(/:/g, "\\:").replace(/\./g, "\\."); // fix for courses which use url_names with colons & periods, e.g. problem:question1, question1.1
|
||||
var answer;
|
||||
if (!$.isArray(value)) {
|
||||
answer = that.$("#answer_" + safeKey + ", #solution_" + safeKey);
|
||||
edx.HtmlUtils.setHtml(answer, edx.HtmlUtils.HTML(value));
|
||||
Collapsible.setCollapsibles(answer);
|
||||
|
||||
// MathJax preprocessor is loaded by 'setupInputTypes'
|
||||
preprocessorTag = 'inputtype_' + elid;
|
||||
mathjaxPreprocessor = this.inputtypeDisplays[preprocessorTag];
|
||||
if (typeof MathJax !== 'undefined' && MathJax !== null && MathJax.Hub.getAllJax(target)[0]) {
|
||||
jax = MathJax.Hub.getAllJax(target)[0];
|
||||
eqn = $(element).val();
|
||||
if (mathjaxPreprocessor) {
|
||||
eqn = mathjaxPreprocessor(eqn);
|
||||
}
|
||||
MathJax.Hub.Queue(['Text', jax, eqn], [this.updateMathML, jax, element]);
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.updateMathML = function(jax, element) {
|
||||
// Sometimes, `value` is just a string containing a MathJax formula.
|
||||
// If this is the case, jQuery will throw an error in some corner cases
|
||||
// because of an incorrect selector. We setup a try..catch so that
|
||||
// the script doesn't break in such cases.
|
||||
//
|
||||
// We will fallback to the second `if statement` below, if an
|
||||
// error is thrown by jQuery.
|
||||
try {
|
||||
$('#' + element.id + '_dynamath').val(jax.root.toMathML(''));
|
||||
} catch (exception) {
|
||||
if (!exception.restart) {
|
||||
throw exception;
|
||||
}
|
||||
if (typeof MathJax !== 'undefined' && MathJax !== null) {
|
||||
MathJax.Callback.After([this.refreshMath, jax], exception.restart);
|
||||
}
|
||||
return $(value).find(".detailed-solution");
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.refreshAnswers = function() {
|
||||
this.$('input.schematic').each(function(index, element) {
|
||||
return element.schematic.update_value();
|
||||
});
|
||||
this.$('.CodeMirror').each(function(index, element) {
|
||||
if (element.CodeMirror.save) {
|
||||
element.CodeMirror.save();
|
||||
}
|
||||
});
|
||||
this.answers = this.inputs.serialize();
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to check available answers and if something is checked (or the answer is set in some textbox),
|
||||
* the "Submit" button becomes enabled. Otherwise it is disabled by default.
|
||||
*
|
||||
* Arguments:
|
||||
* bind (boolean): used on the first check to attach event handlers to input fields
|
||||
* to change "Submit" enable status in case of some manipulations with answers
|
||||
*/
|
||||
Problem.prototype.submitAnswersAndSubmitButton = function(bind) {
|
||||
var answered, atLeastOneTextInputFound, oneTextInputFilled,
|
||||
that = this;
|
||||
if (bind === null || bind === undefined) {
|
||||
bind = false; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
answered = true;
|
||||
atLeastOneTextInputFound = false;
|
||||
oneTextInputFilled = false;
|
||||
this.el.find('input:text').each(function(i, textField) {
|
||||
if ($(textField).is(':visible')) {
|
||||
atLeastOneTextInputFound = true;
|
||||
if ($(textField).val() !== '') {
|
||||
oneTextInputFilled = true;
|
||||
}
|
||||
if (bind) {
|
||||
$(textField).on('input', function() {
|
||||
that.saveNotification.hide();
|
||||
that.showAnswerNotification.hide();
|
||||
that.submitAnswersAndSubmitButton();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
if (atLeastOneTextInputFound && !oneTextInputFilled) {
|
||||
answered = false;
|
||||
}
|
||||
this.el.find('.choicegroup').each(function(i, choicegroupBlock) {
|
||||
var checked;
|
||||
checked = false;
|
||||
$(choicegroupBlock).find('input[type=checkbox], input[type=radio]')
|
||||
.each(function(j, checkboxOrRadio) {
|
||||
if ($(checkboxOrRadio).is(':checked')) {
|
||||
checked = true;
|
||||
}
|
||||
if (bind) {
|
||||
$(checkboxOrRadio).on('click', function() {
|
||||
that.saveNotification.hide();
|
||||
that.el.find('.show').removeAttr('disabled');
|
||||
that.showAnswerNotification.hide();
|
||||
that.submitAnswersAndSubmitButton();
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!checked) {
|
||||
answered = false;
|
||||
}
|
||||
});
|
||||
this.el.find('select').each(function(i, selectField) {
|
||||
var selectedOption = $(selectField).find('option:selected').text()
|
||||
.trim();
|
||||
if (selectedOption === 'Select an option') {
|
||||
answered = false;
|
||||
}
|
||||
if (bind) {
|
||||
$(selectField).on('change', function() {
|
||||
that.saveNotification.hide();
|
||||
that.showAnswerNotification.hide();
|
||||
that.submitAnswersAndSubmitButton();
|
||||
});
|
||||
}
|
||||
});
|
||||
if (answered) {
|
||||
return this.enableSubmitButton(true);
|
||||
// TODO remove the above once everything is extracted into its own
|
||||
// inputtype functions.
|
||||
}
|
||||
});
|
||||
that.el.find(".capa_inputtype").each(function (index, inputtype) {
|
||||
var classes, cls, display, showMethod, i, len, results;
|
||||
classes = $(inputtype).attr("class").split(" ");
|
||||
results = [];
|
||||
for (i = 0, len = classes.length; i < len; i++) {
|
||||
cls = classes[i];
|
||||
display = that.inputtypeDisplays[$(inputtype).attr("id")];
|
||||
showMethod = that.inputtypeShowAnswerMethods[cls];
|
||||
if (showMethod != null) {
|
||||
results.push(showMethod(inputtype, display, answers, response.correct_status_html));
|
||||
} else {
|
||||
return this.enableSubmitButton(false, false);
|
||||
// eslint-disable-next-line no-void
|
||||
results.push(void 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
return results;
|
||||
});
|
||||
if (typeof MathJax !== "undefined" && MathJax !== null) {
|
||||
that.el.find(".problem > div").each(function (index, element) {
|
||||
return MathJax.Hub.Queue(["Typeset", MathJax.Hub, element]);
|
||||
});
|
||||
}
|
||||
that.el.find(".show").attr("disabled", "disabled");
|
||||
that.updateProgress(response);
|
||||
that.clear_all_notifications();
|
||||
that.showAnswerNotification.show();
|
||||
that.focus_on_notification("show-answer");
|
||||
});
|
||||
};
|
||||
|
||||
Problem.prototype.bindResetCorrectness = function() {
|
||||
// Loop through all input types.
|
||||
// Bind the reset functions at that scope.
|
||||
var $inputtypes,
|
||||
that = this;
|
||||
$inputtypes = this.el.find('.capa_inputtype').add(this.el.find('.inputtype'));
|
||||
return $inputtypes.each(function(index, inputtype) {
|
||||
var bindMethod, classes, cls, i, len, results;
|
||||
classes = $(inputtype).attr('class').split(' ');
|
||||
results = [];
|
||||
for (i = 0, len = classes.length; i < len; i++) {
|
||||
cls = classes[i];
|
||||
bindMethod = that.bindResetCorrectnessByInputtype[cls];
|
||||
if (bindMethod != null) {
|
||||
results.push(bindMethod(inputtype));
|
||||
} else {
|
||||
// eslint-disable-next-line no-void
|
||||
results.push(void 0);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
Problem.prototype.clear_all_notifications = function () {
|
||||
this.submitNotification.remove();
|
||||
this.gentleAlertNotification.hide();
|
||||
this.saveNotification.hide();
|
||||
this.showAnswerNotification.hide();
|
||||
};
|
||||
|
||||
Problem.prototype.gentle_alert = function (msg) {
|
||||
edx.HtmlUtils.setHtml(this.el.find(".notification-gentle-alert .notification-message"), edx.HtmlUtils.HTML(msg));
|
||||
this.clear_all_notifications();
|
||||
this.gentleAlertNotification.show();
|
||||
this.gentleAlertNotification.focus();
|
||||
};
|
||||
|
||||
Problem.prototype.save = function () {
|
||||
if (!this.submit_save_waitfor(this.save_internal)) {
|
||||
this.disableAllButtonsWhileRunning(this.save_internal, false);
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.save_internal = function () {
|
||||
var that = this;
|
||||
Logger.log("problem_save", this.answers);
|
||||
return $.postWithPrefix("" + this.url + "/problem_save", this.answers, function (response) {
|
||||
var saveMessage;
|
||||
saveMessage = response.msg;
|
||||
if (response.success) {
|
||||
that.el.trigger("contentChanged", [that.id, response.html, response]);
|
||||
edx.HtmlUtils.setHtml(
|
||||
that.el.find(".notification-save .notification-message"),
|
||||
edx.HtmlUtils.HTML(saveMessage),
|
||||
);
|
||||
that.clear_all_notifications();
|
||||
that.el.find(".wrapper-problem-response .message").hide();
|
||||
that.saveNotification.show();
|
||||
that.focus_on_save_notification();
|
||||
} else {
|
||||
that.gentle_alert(saveMessage);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Problem.prototype.refreshMath = function (event, element) {
|
||||
var elid, eqn, jax, mathjaxPreprocessor, preprocessorTag, target;
|
||||
if (!element) {
|
||||
element = event.target; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
elid = element.id.replace(/^input_/, "");
|
||||
target = "display_" + elid;
|
||||
|
||||
// MathJax preprocessor is loaded by 'setupInputTypes'
|
||||
preprocessorTag = "inputtype_" + elid;
|
||||
mathjaxPreprocessor = this.inputtypeDisplays[preprocessorTag];
|
||||
if (typeof MathJax !== "undefined" && MathJax !== null && MathJax.Hub.getAllJax(target)[0]) {
|
||||
jax = MathJax.Hub.getAllJax(target)[0];
|
||||
eqn = $(element).val();
|
||||
if (mathjaxPreprocessor) {
|
||||
eqn = mathjaxPreprocessor(eqn);
|
||||
}
|
||||
MathJax.Hub.Queue(["Text", jax, eqn], [this.updateMathML, jax, element]);
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.updateMathML = function (jax, element) {
|
||||
try {
|
||||
$("#" + element.id + "_dynamath").val(jax.root.toMathML(""));
|
||||
} catch (exception) {
|
||||
if (!exception.restart) {
|
||||
throw exception;
|
||||
}
|
||||
if (typeof MathJax !== "undefined" && MathJax !== null) {
|
||||
MathJax.Callback.After([this.refreshMath, jax], exception.restart);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.refreshAnswers = function () {
|
||||
this.$("input.schematic").each(function (index, element) {
|
||||
return element.schematic.update_value();
|
||||
});
|
||||
this.$(".CodeMirror").each(function (index, element) {
|
||||
if (element.CodeMirror.save) {
|
||||
element.CodeMirror.save();
|
||||
}
|
||||
});
|
||||
this.answers = this.inputs.serialize();
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to check available answers and if something is checked (or the answer is set in some textbox),
|
||||
* the "Submit" button becomes enabled. Otherwise it is disabled by default.
|
||||
*
|
||||
* Arguments:
|
||||
* bind (boolean): used on the first check to attach event handlers to input fields
|
||||
* to change "Submit" enable status in case of some manipulations with answers
|
||||
*/
|
||||
Problem.prototype.submitAnswersAndSubmitButton = function (bind) {
|
||||
var answered,
|
||||
atLeastOneTextInputFound,
|
||||
oneTextInputFilled,
|
||||
that = this;
|
||||
if (bind === null || bind === undefined) {
|
||||
bind = false; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
answered = true;
|
||||
atLeastOneTextInputFound = false;
|
||||
oneTextInputFilled = false;
|
||||
this.el.find("input:text").each(function (i, textField) {
|
||||
if ($(textField).is(":visible")) {
|
||||
atLeastOneTextInputFound = true;
|
||||
if ($(textField).val() !== "") {
|
||||
oneTextInputFilled = true;
|
||||
}
|
||||
if (bind) {
|
||||
$(textField).on("input", function () {
|
||||
that.saveNotification.hide();
|
||||
that.showAnswerNotification.hide();
|
||||
that.submitAnswersAndSubmitButton();
|
||||
});
|
||||
};
|
||||
|
||||
// Find all places where each input type displays its correct-ness
|
||||
// Replace them with their original state--'unanswered'.
|
||||
Problem.prototype.bindResetCorrectnessByInputtype = {
|
||||
// These are run at the scope of the capa inputtype
|
||||
// They should set handlers on each <input> to reset the whole.
|
||||
formulaequationinput: function(element) {
|
||||
return $(element).find('input').on('input', function() {
|
||||
var $p;
|
||||
$p = $(element).find('span.status');
|
||||
$p.removeClass('correct incorrect submitted');
|
||||
return $p.parent().removeAttr('class').addClass('unsubmitted');
|
||||
});
|
||||
},
|
||||
choicegroup: function(element) {
|
||||
var $element, id;
|
||||
$element = $(element);
|
||||
id = ($element.attr('id').match(/^inputtype_(.*)$/))[1];
|
||||
return $element.find('input').on('change', function() {
|
||||
var $status;
|
||||
$status = $('#status_' + id);
|
||||
if ($status[0]) {
|
||||
$status.removeAttr('class').addClass('status unanswered');
|
||||
} else {
|
||||
$('<span>', {
|
||||
class: 'status unanswered',
|
||||
style: 'display: inline-block;',
|
||||
id: 'status_' + id
|
||||
});
|
||||
}
|
||||
$element.find('label').find('span.status.correct').remove();
|
||||
return $element.find('label').removeAttr('class');
|
||||
});
|
||||
},
|
||||
'option-input': function(element) {
|
||||
var $select, id;
|
||||
$select = $(element).find('select');
|
||||
id = ($select.attr('id').match(/^input_(.*)$/))[1];
|
||||
return $select.on('change', function() {
|
||||
return $('#status_' + id).removeAttr('class').addClass('unanswered')
|
||||
.find('.sr')
|
||||
.text(gettext('unsubmitted'));
|
||||
});
|
||||
},
|
||||
textline: function(element) {
|
||||
return $(element).find('input').on('input', function() {
|
||||
var $p;
|
||||
$p = $(element).find('span.status');
|
||||
$p.removeClass('correct incorrect submitted');
|
||||
return $p.parent().removeClass('correct incorrect').addClass('unsubmitted');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
if (atLeastOneTextInputFound && !oneTextInputFilled) {
|
||||
answered = false;
|
||||
}
|
||||
this.el.find(".choicegroup").each(function (i, choicegroupBlock) {
|
||||
var checked;
|
||||
checked = false;
|
||||
$(choicegroupBlock)
|
||||
.find("input[type=checkbox], input[type=radio]")
|
||||
.each(function (j, checkboxOrRadio) {
|
||||
if ($(checkboxOrRadio).is(":checked")) {
|
||||
checked = true;
|
||||
}
|
||||
};
|
||||
if (bind) {
|
||||
$(checkboxOrRadio).on("click", function () {
|
||||
that.saveNotification.hide();
|
||||
that.el.find(".show").removeAttr("disabled");
|
||||
that.showAnswerNotification.hide();
|
||||
that.submitAnswersAndSubmitButton();
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!checked) {
|
||||
answered = false;
|
||||
}
|
||||
});
|
||||
this.el.find("select").each(function (i, selectField) {
|
||||
var selectedOption = $(selectField).find("option:selected").text().trim();
|
||||
if (selectedOption === "Select an option") {
|
||||
answered = false;
|
||||
}
|
||||
if (bind) {
|
||||
$(selectField).on("change", function () {
|
||||
that.saveNotification.hide();
|
||||
that.showAnswerNotification.hide();
|
||||
that.submitAnswersAndSubmitButton();
|
||||
});
|
||||
}
|
||||
});
|
||||
if (answered) {
|
||||
return this.enableSubmitButton(true);
|
||||
} else {
|
||||
return this.enableSubmitButton(false, false);
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.inputtypeSetupMethods = {
|
||||
'text-input-dynamath': function(element) {
|
||||
/*
|
||||
Problem.prototype.bindResetCorrectness = function () {
|
||||
// Loop through all input types.
|
||||
// Bind the reset functions at that scope.
|
||||
var $inputtypes,
|
||||
that = this;
|
||||
$inputtypes = this.el.find(".capa_inputtype").add(this.el.find(".inputtype"));
|
||||
return $inputtypes.each(function (index, inputtype) {
|
||||
var bindMethod, classes, cls, i, len, results;
|
||||
classes = $(inputtype).attr("class").split(" ");
|
||||
results = [];
|
||||
for (i = 0, len = classes.length; i < len; i++) {
|
||||
cls = classes[i];
|
||||
bindMethod = that.bindResetCorrectnessByInputtype[cls];
|
||||
if (bindMethod != null) {
|
||||
results.push(bindMethod(inputtype));
|
||||
} else {
|
||||
// eslint-disable-next-line no-void
|
||||
results.push(void 0);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
});
|
||||
};
|
||||
|
||||
// Find all places where each input type displays its correct-ness
|
||||
// Replace them with their original state--'unanswered'.
|
||||
Problem.prototype.bindResetCorrectnessByInputtype = {
|
||||
// These are run at the scope of the capa inputtype
|
||||
// They should set handlers on each <input> to reset the whole.
|
||||
formulaequationinput: function (element) {
|
||||
return $(element)
|
||||
.find("input")
|
||||
.on("input", function () {
|
||||
var $p;
|
||||
$p = $(element).find("span.status");
|
||||
$p.removeClass("correct incorrect submitted");
|
||||
return $p.parent().removeAttr("class").addClass("unsubmitted");
|
||||
});
|
||||
},
|
||||
choicegroup: function (element) {
|
||||
var $element, id;
|
||||
$element = $(element);
|
||||
id = $element.attr("id").match(/^inputtype_(.*)$/)[1];
|
||||
return $element.find("input").on("change", function () {
|
||||
var $status;
|
||||
$status = $("#status_" + id);
|
||||
if ($status[0]) {
|
||||
$status.removeAttr("class").addClass("status unanswered");
|
||||
} else {
|
||||
$("<span>", {
|
||||
class: "status unanswered",
|
||||
style: "display: inline-block;",
|
||||
id: "status_" + id,
|
||||
});
|
||||
}
|
||||
$element.find("label").find("span.status.correct").remove();
|
||||
return $element.find("label").removeAttr("class");
|
||||
});
|
||||
},
|
||||
"option-input": function (element) {
|
||||
var $select, id;
|
||||
$select = $(element).find("select");
|
||||
id = $select.attr("id").match(/^input_(.*)$/)[1];
|
||||
return $select.on("change", function () {
|
||||
return $("#status_" + id)
|
||||
.removeAttr("class")
|
||||
.addClass("unanswered")
|
||||
.find(".sr")
|
||||
.text(gettext("unsubmitted"));
|
||||
});
|
||||
},
|
||||
textline: function (element) {
|
||||
return $(element)
|
||||
.find("input")
|
||||
.on("input", function () {
|
||||
var $p;
|
||||
$p = $(element).find("span.status");
|
||||
$p.removeClass("correct incorrect submitted");
|
||||
return $p.parent().removeClass("correct incorrect").addClass("unsubmitted");
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
Problem.prototype.inputtypeSetupMethods = {
|
||||
"text-input-dynamath": function (element) {
|
||||
/*
|
||||
Return: function (eqn) -> eqn that preprocesses the user formula input before
|
||||
it is fed into MathJax. Return 'false' if no preprocessor specified
|
||||
*/
|
||||
var data, preprocessor, preprocessorClass, preprocessorClassName;
|
||||
data = $(element).find('.text-input-dynamath_data');
|
||||
preprocessorClassName = data.data('preprocessor');
|
||||
preprocessorClass = window[preprocessorClassName];
|
||||
if (preprocessorClass == null) {
|
||||
return false;
|
||||
} else {
|
||||
preprocessor = new preprocessorClass();
|
||||
return preprocessor.fn;
|
||||
}
|
||||
var data, preprocessor, preprocessorClass, preprocessorClassName;
|
||||
data = $(element).find(".text-input-dynamath_data");
|
||||
preprocessorClassName = data.data("preprocessor");
|
||||
preprocessorClass = window[preprocessorClassName];
|
||||
if (preprocessorClass == null) {
|
||||
return false;
|
||||
} else {
|
||||
preprocessor = new preprocessorClass();
|
||||
return preprocessor.fn;
|
||||
}
|
||||
},
|
||||
cminput: function (container) {
|
||||
var CodeMirrorEditor, CodeMirrorTextArea, element, id, linenumbers, mode, spaces, tabsize;
|
||||
element = $(container).find("textarea");
|
||||
tabsize = element.data("tabsize");
|
||||
mode = element.data("mode");
|
||||
linenumbers = element.data("linenums");
|
||||
spaces = Array(parseInt(tabsize, 10) + 1).join(" ");
|
||||
CodeMirrorEditor = CodeMirror.fromTextArea(element[0], {
|
||||
lineNumbers: linenumbers,
|
||||
indentUnit: tabsize,
|
||||
tabSize: tabsize,
|
||||
mode: mode,
|
||||
matchBrackets: true,
|
||||
lineWrapping: true,
|
||||
indentWithTabs: false,
|
||||
smartIndent: false,
|
||||
extraKeys: {
|
||||
Esc: function () {
|
||||
$(".grader-status").focus();
|
||||
return false;
|
||||
},
|
||||
cminput: function(container) {
|
||||
var CodeMirrorEditor, CodeMirrorTextArea, element, id, linenumbers, mode, spaces, tabsize;
|
||||
element = $(container).find('textarea');
|
||||
tabsize = element.data('tabsize');
|
||||
mode = element.data('mode');
|
||||
linenumbers = element.data('linenums');
|
||||
spaces = Array(parseInt(tabsize, 10) + 1).join(' ');
|
||||
CodeMirrorEditor = CodeMirror.fromTextArea(element[0], {
|
||||
lineNumbers: linenumbers,
|
||||
indentUnit: tabsize,
|
||||
tabSize: tabsize,
|
||||
mode: mode,
|
||||
matchBrackets: true,
|
||||
lineWrapping: true,
|
||||
indentWithTabs: false,
|
||||
smartIndent: false,
|
||||
extraKeys: {
|
||||
Esc: function() {
|
||||
$('.grader-status').focus();
|
||||
return false;
|
||||
},
|
||||
Tab: function(cm) {
|
||||
cm.replaceSelection(spaces, 'end');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
id = element.attr('id').replace(/^input_/, '');
|
||||
CodeMirrorTextArea = CodeMirrorEditor.getInputField();
|
||||
CodeMirrorTextArea.setAttribute('id', 'cm-textarea-' + id);
|
||||
CodeMirrorTextArea.setAttribute('aria-describedby', 'cm-editor-exit-message-' + id + ' status_' + id);
|
||||
return CodeMirrorEditor;
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.inputtypeShowAnswerMethods = {
|
||||
choicegroup: function(element, display, answers, correctStatusHtml) {
|
||||
var answer, choice, inputId, i, len, results, $element, $inputLabel, $inputStatus;
|
||||
$element = $(element);
|
||||
inputId = $element.attr('id').replace(/inputtype_/, '');
|
||||
inputId = inputId.replace(':', '\\:'); // fix for courses which use url_names with colons, e.g. problem:question1
|
||||
var safeId = inputId.replace(/\./g, '\\.'); // fix for courses which use url_names with periods. e.g. question1.1
|
||||
answer = answers[inputId];
|
||||
results = [];
|
||||
for (i = 0, len = answer.length; i < len; i++) {
|
||||
choice = answer[i];
|
||||
$inputLabel = $element.find('#input_' + safeId + '_' + choice + ' + label');
|
||||
$inputStatus = $element.find('#status_' + safeId);
|
||||
// If the correct answer was already Submitted before "Show Answer" was selected,
|
||||
// the status HTML will already be present. Otherwise, inject the status HTML.
|
||||
|
||||
// If the learner clicked a different answer after Submit, their submitted answers
|
||||
// will be marked as "unanswered". In that case, for correct answers update the
|
||||
// classes accordingly.
|
||||
if ($inputStatus.hasClass('unanswered')) {
|
||||
edx.HtmlUtils.append($inputLabel, edx.HtmlUtils.HTML(correctStatusHtml));
|
||||
$inputLabel.addClass('choicegroup_correct');
|
||||
} else if (!$inputLabel.hasClass('choicegroup_correct')) {
|
||||
// If the status HTML is not already present (due to clicking Submit), append
|
||||
// the status HTML for correct answers.
|
||||
edx.HtmlUtils.append($inputLabel, edx.HtmlUtils.HTML(correctStatusHtml));
|
||||
$inputLabel.removeClass('choicegroup_incorrect');
|
||||
results.push($inputLabel.addClass('choicegroup_correct'));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
Tab: function (cm) {
|
||||
cm.replaceSelection(spaces, "end");
|
||||
return false;
|
||||
},
|
||||
choicetextgroup: function(element, display, answers) {
|
||||
var answer, choice, inputId, i, len, results, $element;
|
||||
$element = $(element);
|
||||
inputId = $element.attr('id').replace(/inputtype_/, '');
|
||||
answer = answers[inputId];
|
||||
results = [];
|
||||
for (i = 0, len = answer.length; i < len; i++) {
|
||||
choice = answer[i];
|
||||
results.push($element.find('section#forinput' + choice).addClass('choicetextgroup_show_correct'));
|
||||
}
|
||||
return results;
|
||||
},
|
||||
imageinput: function(element, display, answers) {
|
||||
// answers is a dict of (answer_id, answer_text) for each answer for this question.
|
||||
//
|
||||
// @Examples:
|
||||
// {'anwser_id': {
|
||||
// 'rectangle': '(10,10)-(20,30);(12,12)-(40,60)',
|
||||
// 'regions': '[[10,10], [30,30], [10, 30], [30, 10]]'
|
||||
// } }
|
||||
var canvas, container, id, types, context, $element;
|
||||
types = {
|
||||
rectangle: function(ctx, coords) {
|
||||
var rects, reg;
|
||||
reg = /^\(([0-9]+),([0-9]+)\)-\(([0-9]+),([0-9]+)\)$/;
|
||||
rects = coords.replace(/\s*/g, '').split(/;/);
|
||||
$.each(rects, function(index, rect) {
|
||||
var abs, height, points, width;
|
||||
abs = Math.abs;
|
||||
points = reg.exec(rect);
|
||||
if (points) {
|
||||
width = abs(points[3] - points[1]);
|
||||
height = abs(points[4] - points[2]);
|
||||
ctx.rect(points[1], points[2], width, height);
|
||||
}
|
||||
});
|
||||
ctx.stroke();
|
||||
return ctx.fill();
|
||||
},
|
||||
regions: function(ctx, coords) {
|
||||
var parseCoords;
|
||||
parseCoords = function(coordinates) {
|
||||
var reg;
|
||||
reg = JSON.parse(coordinates);
|
||||
},
|
||||
});
|
||||
id = element.attr("id").replace(/^input_/, "");
|
||||
CodeMirrorTextArea = CodeMirrorEditor.getInputField();
|
||||
CodeMirrorTextArea.setAttribute("id", "cm-textarea-" + id);
|
||||
CodeMirrorTextArea.setAttribute("aria-describedby", "cm-editor-exit-message-" + id + " status_" + id);
|
||||
return CodeMirrorEditor;
|
||||
},
|
||||
};
|
||||
|
||||
// Regions is list of lists [region1, region2, region3, ...] where regionN
|
||||
// is disordered list of points: [[1,1], [100,100], [50,50], [20, 70]].
|
||||
// If there is only one region in the list, simpler notation can be used:
|
||||
// regions="[[10,10], [30,30], [10, 30], [30, 10]]" (without explicitly
|
||||
// setting outer list)
|
||||
if (typeof reg[0][0][0] === 'undefined') {
|
||||
// we have [[1,2],[3,4],[5,6]] - single region
|
||||
// instead of [[[1,2],[3,4],[5,6], [[1,2],[3,4],[5,6]]]
|
||||
// or [[[1,2],[3,4],[5,6]]] - multiple regions syntax
|
||||
reg = [reg];
|
||||
}
|
||||
return reg;
|
||||
};
|
||||
return $.each(parseCoords(coords), function(index, region) {
|
||||
ctx.beginPath();
|
||||
$.each(region, function(idx, point) {
|
||||
if (idx === 0) {
|
||||
return ctx.moveTo(point[0], point[1]);
|
||||
} else {
|
||||
return ctx.lineTo(point[0], point[1]);
|
||||
}
|
||||
});
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
return ctx.fill();
|
||||
});
|
||||
}
|
||||
};
|
||||
$element = $(element);
|
||||
id = $element.attr('id').replace(/inputtype_/, '');
|
||||
container = $element.find('#answer_' + id);
|
||||
canvas = document.createElement('canvas');
|
||||
canvas.width = container.data('width');
|
||||
canvas.height = container.data('height');
|
||||
if (canvas.getContext) {
|
||||
context = canvas.getContext('2d');
|
||||
} else {
|
||||
console.log('Canvas is not supported.'); // eslint-disable-line no-console
|
||||
}
|
||||
context.fillStyle = 'rgba(255,255,255,.3)';
|
||||
context.strokeStyle = '#FF0000';
|
||||
context.lineWidth = '2';
|
||||
if (answers[id]) {
|
||||
$.each(answers[id], function(key, value) {
|
||||
if ((types[key] !== null && types[key] !== undefined) && value) {
|
||||
types[key](context, value);
|
||||
}
|
||||
});
|
||||
edx.HtmlUtils.setHtml(container, edx.HtmlUtils.HTML(canvas));
|
||||
} else {
|
||||
console.log('Answer is absent for image input with id=' + id); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
};
|
||||
Problem.prototype.inputtypeShowAnswerMethods = {
|
||||
choicegroup: function (element, display, answers, correctStatusHtml) {
|
||||
var answer, choice, inputId, i, len, results, $element, $inputLabel, $inputStatus;
|
||||
$element = $(element);
|
||||
inputId = $element.attr("id").replace(/inputtype_/, "");
|
||||
var safeId = inputId.replace(/\\/g, "\\\\").replace(/:/g, "\\:").replace(/\./g, "\\."); // fix for courses which use url_names with colons & periods, e.g. problem:question1, question1.1
|
||||
answer = answers[inputId];
|
||||
results = [];
|
||||
for (i = 0, len = answer.length; i < len; i++) {
|
||||
choice = answer[i];
|
||||
$inputLabel = $element.find("#input_" + safeId + "_" + choice + " + label");
|
||||
$inputStatus = $element.find("#status_" + safeId);
|
||||
// If the correct answer was already Submitted before "Show Answer" was selected,
|
||||
// the status HTML will already be present. Otherwise, inject the status HTML.
|
||||
|
||||
/**
|
||||
* Used to keep the buttons disabled while operationCallback is running.
|
||||
*
|
||||
* params:
|
||||
* 'operationCallback' is an operation to be run.
|
||||
* isFromCheckOperation' is a boolean to keep track if 'operationCallback' was
|
||||
* from submit, if so then text of submit button will be changed as well.
|
||||
*
|
||||
*/
|
||||
Problem.prototype.disableAllButtonsWhileRunning = function(operationCallback, isFromCheckOperation) {
|
||||
var that = this;
|
||||
var allButtons = [this.resetButton, this.saveButton, this.showButton, this.hintButton, this.submitButton];
|
||||
var initiallyEnabledButtons = allButtons.filter(function(button) {
|
||||
return !button.attr('disabled');
|
||||
// If the learner clicked a different answer after Submit, their submitted answers
|
||||
// will be marked as "unanswered". In that case, for correct answers update the
|
||||
// classes accordingly.
|
||||
if ($inputStatus.hasClass("unanswered")) {
|
||||
edx.HtmlUtils.append($inputLabel, edx.HtmlUtils.HTML(correctStatusHtml));
|
||||
$inputLabel.addClass("choicegroup_correct");
|
||||
} else if (!$inputLabel.hasClass("choicegroup_correct")) {
|
||||
// If the status HTML is not already present (due to clicking Submit), append
|
||||
// the status HTML for correct answers.
|
||||
edx.HtmlUtils.append($inputLabel, edx.HtmlUtils.HTML(correctStatusHtml));
|
||||
$inputLabel.removeClass("choicegroup_incorrect");
|
||||
results.push($inputLabel.addClass("choicegroup_correct"));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
choicetextgroup: function (element, display, answers) {
|
||||
var answer, choice, inputId, i, len, results, $element;
|
||||
$element = $(element);
|
||||
inputId = $element.attr("id").replace(/inputtype_/, "");
|
||||
answer = answers[inputId];
|
||||
results = [];
|
||||
for (i = 0, len = answer.length; i < len; i++) {
|
||||
choice = answer[i];
|
||||
results.push($element.find("section#forinput" + choice).addClass("choicetextgroup_show_correct"));
|
||||
}
|
||||
return results;
|
||||
},
|
||||
imageinput: function (element, display, answers) {
|
||||
// answers is a dict of (answer_id, answer_text) for each answer for this question.
|
||||
//
|
||||
// @Examples:
|
||||
// {'anwser_id': {
|
||||
// 'rectangle': '(10,10)-(20,30);(12,12)-(40,60)',
|
||||
// 'regions': '[[10,10], [30,30], [10, 30], [30, 10]]'
|
||||
// } }
|
||||
var canvas, container, id, types, context, $element;
|
||||
types = {
|
||||
rectangle: function (ctx, coords) {
|
||||
var rects, reg;
|
||||
reg = /^\(([0-9]+),([0-9]+)\)-\(([0-9]+),([0-9]+)\)$/;
|
||||
rects = coords.replace(/\s*/g, "").split(/;/);
|
||||
$.each(rects, function (index, rect) {
|
||||
var abs, height, points, width;
|
||||
abs = Math.abs;
|
||||
points = reg.exec(rect);
|
||||
if (points) {
|
||||
width = abs(points[3] - points[1]);
|
||||
height = abs(points[4] - points[2]);
|
||||
ctx.rect(points[1], points[2], width, height);
|
||||
}
|
||||
});
|
||||
this.enableButtons(initiallyEnabledButtons, false, isFromCheckOperation);
|
||||
return operationCallback().always(function() {
|
||||
return that.enableButtons(initiallyEnabledButtons, true, isFromCheckOperation);
|
||||
});
|
||||
};
|
||||
ctx.stroke();
|
||||
return ctx.fill();
|
||||
},
|
||||
regions: function (ctx, coords) {
|
||||
var parseCoords;
|
||||
parseCoords = function (coordinates) {
|
||||
var reg;
|
||||
reg = JSON.parse(coordinates);
|
||||
|
||||
/**
|
||||
* Enables/disables buttons by removing/adding the disabled attribute. The submit button is checked
|
||||
* separately due to the changing text it contains.
|
||||
*
|
||||
* params:
|
||||
* 'buttons' is an array of buttons that will have their 'disabled' attribute modified
|
||||
* 'enable' a boolean to either enable or disable the buttons passed in the first parameter
|
||||
* 'changeSubmitButtonText' is a boolean to keep track if operation was initiated
|
||||
* from submit so that text of submit button will also be changed while disabling/enabling
|
||||
* the submit button.
|
||||
*/
|
||||
Problem.prototype.enableButtons = function(buttons, enable, changeSubmitButtonText) {
|
||||
var that = this;
|
||||
buttons.forEach(function(button) {
|
||||
if (button.hasClass('submit')) {
|
||||
that.enableSubmitButton(enable, changeSubmitButtonText);
|
||||
} else if (enable) {
|
||||
button.removeAttr('disabled');
|
||||
} else {
|
||||
button.attr({disabled: 'disabled'});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to disable submit button to reduce chance of accidental double-submissions.
|
||||
*
|
||||
* params:
|
||||
* 'enable' is a boolean to determine enabling/disabling of submit button.
|
||||
* 'changeText' is a boolean to determine if there is need to change the
|
||||
* text of submit button as well.
|
||||
*/
|
||||
Problem.prototype.enableSubmitButton = function(enable, changeText) {
|
||||
var submitCanBeEnabled;
|
||||
if (changeText === null || changeText === undefined) {
|
||||
changeText = true; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
if (enable) {
|
||||
submitCanBeEnabled = this.submitButton.data('should-enable-submit-button') === 'True';
|
||||
if (submitCanBeEnabled) {
|
||||
this.submitButton.removeAttr('disabled');
|
||||
}
|
||||
if (changeText) {
|
||||
this.submitButtonLabel.text(this.submitButtonSubmitText);
|
||||
}
|
||||
} else {
|
||||
this.submitButton.attr({disabled: 'disabled'});
|
||||
if (changeText) {
|
||||
this.submitButtonLabel.text(this.submitButtonSubmittingText);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.enableSubmitButtonAfterResponse = function() {
|
||||
this.has_response = true;
|
||||
if (!this.has_timed_out) {
|
||||
// Server has returned response before our timeout.
|
||||
return this.enableSubmitButton(false);
|
||||
} else {
|
||||
return this.enableSubmitButton(true);
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.enableSubmitButtonAfterTimeout = function() {
|
||||
var enableSubmitButton,
|
||||
that = this;
|
||||
this.has_timed_out = false;
|
||||
this.has_response = false;
|
||||
enableSubmitButton = function() {
|
||||
that.has_timed_out = true;
|
||||
if (that.has_response) {
|
||||
that.enableSubmitButton(true);
|
||||
}
|
||||
// Regions is list of lists [region1, region2, region3, ...] where regionN
|
||||
// is disordered list of points: [[1,1], [100,100], [50,50], [20, 70]].
|
||||
// If there is only one region in the list, simpler notation can be used:
|
||||
// regions="[[10,10], [30,30], [10, 30], [30, 10]]" (without explicitly
|
||||
// setting outer list)
|
||||
if (typeof reg[0][0][0] === "undefined") {
|
||||
// we have [[1,2],[3,4],[5,6]] - single region
|
||||
// instead of [[[1,2],[3,4],[5,6], [[1,2],[3,4],[5,6]]]
|
||||
// or [[[1,2],[3,4],[5,6]]] - multiple regions syntax
|
||||
reg = [reg];
|
||||
}
|
||||
return reg;
|
||||
};
|
||||
return window.setTimeout(enableSubmitButton, 750);
|
||||
};
|
||||
|
||||
Problem.prototype.hint_button = function() {
|
||||
// Store the index of the currently shown hint as an attribute.
|
||||
// Use that to compute the next hint number when the button is clicked.
|
||||
var hintContainer, hintIndex, nextIndex,
|
||||
that = this;
|
||||
hintContainer = this.$('.problem-hint');
|
||||
hintIndex = hintContainer.attr('hint_index');
|
||||
// eslint-disable-next-line no-void
|
||||
if (hintIndex === void 0) {
|
||||
nextIndex = 0;
|
||||
} else {
|
||||
nextIndex = parseInt(hintIndex, 10) + 1;
|
||||
}
|
||||
return $.postWithPrefix('' + this.url + '/hint_button', {
|
||||
hint_index: nextIndex,
|
||||
input_id: this.id
|
||||
}, function(response) {
|
||||
var hintMsgContainer;
|
||||
if (response.success) {
|
||||
hintMsgContainer = that.$('.problem-hint .notification-message');
|
||||
hintContainer.attr('hint_index', response.hint_index);
|
||||
edx.HtmlUtils.setHtml(hintMsgContainer, edx.HtmlUtils.HTML(response.msg));
|
||||
MathJax.Hub.Queue(['Typeset', MathJax.Hub, hintContainer[0]]);
|
||||
if (response.should_enable_next_hint) {
|
||||
that.hintButton.removeAttr('disabled');
|
||||
} else {
|
||||
that.hintButton.attr({disabled: 'disabled'});
|
||||
}
|
||||
that.el.find('.notification-hint').show();
|
||||
that.focus_on_hint_notification(nextIndex);
|
||||
return $.each(parseCoords(coords), function (index, region) {
|
||||
ctx.beginPath();
|
||||
$.each(region, function (idx, point) {
|
||||
if (idx === 0) {
|
||||
return ctx.moveTo(point[0], point[1]);
|
||||
} else {
|
||||
that.gentle_alert(response.msg);
|
||||
return ctx.lineTo(point[0], point[1]);
|
||||
}
|
||||
});
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
return ctx.fill();
|
||||
});
|
||||
},
|
||||
};
|
||||
$element = $(element);
|
||||
id = $element.attr("id").replace(/inputtype_/, "");
|
||||
container = $element.find("#answer_" + id);
|
||||
canvas = document.createElement("canvas");
|
||||
canvas.width = container.data("width");
|
||||
canvas.height = container.data("height");
|
||||
if (canvas.getContext) {
|
||||
context = canvas.getContext("2d");
|
||||
} else {
|
||||
console.log("Canvas is not supported."); // eslint-disable-line no-console
|
||||
}
|
||||
context.fillStyle = "rgba(255,255,255,.3)";
|
||||
context.strokeStyle = "#FF0000";
|
||||
context.lineWidth = "2";
|
||||
if (answers[id]) {
|
||||
$.each(answers[id], function (key, value) {
|
||||
if (types[key] !== null && types[key] !== undefined && value) {
|
||||
types[key](context, value);
|
||||
}
|
||||
});
|
||||
edx.HtmlUtils.setHtml(container, edx.HtmlUtils.HTML(canvas));
|
||||
} else {
|
||||
console.log("Answer is absent for image input with id=" + id); // eslint-disable-line no-console
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return Problem;
|
||||
}).call(this);
|
||||
/**
|
||||
* Used to keep the buttons disabled while operationCallback is running.
|
||||
*
|
||||
* params:
|
||||
* 'operationCallback' is an operation to be run.
|
||||
* isFromCheckOperation' is a boolean to keep track if 'operationCallback' was
|
||||
* from submit, if so then text of submit button will be changed as well.
|
||||
*
|
||||
*/
|
||||
Problem.prototype.disableAllButtonsWhileRunning = function (operationCallback, isFromCheckOperation) {
|
||||
var that = this;
|
||||
var allButtons = [this.resetButton, this.saveButton, this.showButton, this.hintButton, this.submitButton];
|
||||
var initiallyEnabledButtons = allButtons.filter(function (button) {
|
||||
return !button.attr("disabled");
|
||||
});
|
||||
this.enableButtons(initiallyEnabledButtons, false, isFromCheckOperation);
|
||||
return operationCallback().always(function () {
|
||||
return that.enableButtons(initiallyEnabledButtons, true, isFromCheckOperation);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Enables/disables buttons by removing/adding the disabled attribute. The submit button is checked
|
||||
* separately due to the changing text it contains.
|
||||
*
|
||||
* params:
|
||||
* 'buttons' is an array of buttons that will have their 'disabled' attribute modified
|
||||
* 'enable' a boolean to either enable or disable the buttons passed in the first parameter
|
||||
* 'changeSubmitButtonText' is a boolean to keep track if operation was initiated
|
||||
* from submit so that text of submit button will also be changed while disabling/enabling
|
||||
* the submit button.
|
||||
*/
|
||||
Problem.prototype.enableButtons = function (buttons, enable, changeSubmitButtonText) {
|
||||
var that = this;
|
||||
buttons.forEach(function (button) {
|
||||
if (button.hasClass("submit")) {
|
||||
that.enableSubmitButton(enable, changeSubmitButtonText);
|
||||
} else if (enable) {
|
||||
button.removeAttr("disabled");
|
||||
} else {
|
||||
button.attr({ disabled: "disabled" });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to disable submit button to reduce chance of accidental double-submissions.
|
||||
*
|
||||
* params:
|
||||
* 'enable' is a boolean to determine enabling/disabling of submit button.
|
||||
* 'changeText' is a boolean to determine if there is need to change the
|
||||
* text of submit button as well.
|
||||
*/
|
||||
Problem.prototype.enableSubmitButton = function (enable, changeText) {
|
||||
var submitCanBeEnabled;
|
||||
if (changeText === null || changeText === undefined) {
|
||||
changeText = true; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
if (enable) {
|
||||
submitCanBeEnabled = this.submitButton.data("should-enable-submit-button") === "True";
|
||||
if (submitCanBeEnabled) {
|
||||
this.submitButton.removeAttr("disabled");
|
||||
}
|
||||
if (changeText) {
|
||||
this.submitButtonLabel.text(this.submitButtonSubmitText);
|
||||
}
|
||||
} else {
|
||||
this.submitButton.attr({ disabled: "disabled" });
|
||||
if (changeText) {
|
||||
this.submitButtonLabel.text(this.submitButtonSubmittingText);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.enableSubmitButtonAfterResponse = function () {
|
||||
this.has_response = true;
|
||||
if (!this.has_timed_out) {
|
||||
// Server has returned response before our timeout.
|
||||
return this.enableSubmitButton(false);
|
||||
} else {
|
||||
return this.enableSubmitButton(true);
|
||||
}
|
||||
};
|
||||
|
||||
Problem.prototype.enableSubmitButtonAfterTimeout = function () {
|
||||
var enableSubmitButton,
|
||||
that = this;
|
||||
this.has_timed_out = false;
|
||||
this.has_response = false;
|
||||
enableSubmitButton = function () {
|
||||
that.has_timed_out = true;
|
||||
if (that.has_response) {
|
||||
that.enableSubmitButton(true);
|
||||
}
|
||||
};
|
||||
return window.setTimeout(enableSubmitButton, 750);
|
||||
};
|
||||
|
||||
Problem.prototype.hint_button = function () {
|
||||
// Store the index of the currently shown hint as an attribute.
|
||||
// Use that to compute the next hint number when the button is clicked.
|
||||
var hintContainer,
|
||||
hintIndex,
|
||||
nextIndex,
|
||||
that = this;
|
||||
hintContainer = this.$(".problem-hint");
|
||||
hintIndex = hintContainer.attr("hint_index");
|
||||
// eslint-disable-next-line no-void
|
||||
if (hintIndex === void 0) {
|
||||
nextIndex = 0;
|
||||
} else {
|
||||
nextIndex = parseInt(hintIndex, 10) + 1;
|
||||
}
|
||||
return $.postWithPrefix(
|
||||
"" + this.url + "/hint_button",
|
||||
{
|
||||
hint_index: nextIndex,
|
||||
input_id: this.id,
|
||||
},
|
||||
function (response) {
|
||||
var hintMsgContainer;
|
||||
if (response.success) {
|
||||
hintMsgContainer = that.$(".problem-hint .notification-message");
|
||||
hintContainer.attr("hint_index", response.hint_index);
|
||||
edx.HtmlUtils.setHtml(hintMsgContainer, edx.HtmlUtils.HTML(response.msg));
|
||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub, hintContainer[0]]);
|
||||
if (response.should_enable_next_hint) {
|
||||
that.hintButton.removeAttr("disabled");
|
||||
} else {
|
||||
that.hintButton.attr({ disabled: "disabled" });
|
||||
}
|
||||
that.el.find(".notification-hint").show();
|
||||
that.focus_on_hint_notification(nextIndex);
|
||||
} else {
|
||||
that.gentle_alert(response.msg);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return Problem;
|
||||
}.call(this);
|
||||
}).call(this);
|
||||
|
||||
@@ -14,44 +14,41 @@
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line no-shadow-restricted-names
|
||||
window.ImageInput = (function($, undefined) {
|
||||
var ImageInput = ImageInputConstructor;
|
||||
window.ImageInput = function ($, undefined) {
|
||||
var ImageInput = ImageInputConstructor;
|
||||
|
||||
ImageInput.prototype = {
|
||||
constructor: ImageInputConstructor,
|
||||
clickHandler: clickHandler
|
||||
};
|
||||
ImageInput.prototype = {
|
||||
constructor: ImageInputConstructor,
|
||||
clickHandler: clickHandler,
|
||||
};
|
||||
|
||||
return ImageInput;
|
||||
return ImageInput;
|
||||
|
||||
function ImageInputConstructor(elementId) {
|
||||
this.el = $('#imageinput_' + elementId);
|
||||
this.crossEl = $('#cross_' + elementId);
|
||||
this.inputEl = $('#input_' + elementId);
|
||||
function ImageInputConstructor(elementId) {
|
||||
this.el = $("#imageinput_" + elementId);
|
||||
this.crossEl = $("#cross_" + elementId);
|
||||
this.inputEl = $("#input_" + elementId);
|
||||
|
||||
this.el.on('click', this.clickHandler.bind(this));
|
||||
}
|
||||
this.el.on("click", this.clickHandler.bind(this));
|
||||
}
|
||||
|
||||
function clickHandler(event) {
|
||||
var offset = this.el.offset(),
|
||||
posX = event.offsetX
|
||||
? event.offsetX : event.pageX - offset.left,
|
||||
posY = event.offsetY
|
||||
? event.offsetY : event.pageY - offset.top,
|
||||
function clickHandler(event) {
|
||||
var offset = this.el.offset(),
|
||||
posX = event.offsetX ? event.offsetX : event.pageX - offset.left,
|
||||
posY = event.offsetY ? event.offsetY : event.pageY - offset.top,
|
||||
// To reduce differences between values returned by different kinds
|
||||
// of browsers, we round `posX` and `posY`.
|
||||
//
|
||||
// IE10: `posX` and `posY` - float.
|
||||
// Chrome, FF: `posX` and `posY` - integers.
|
||||
result = "[" + Math.round(posX) + "," + Math.round(posY) + "]";
|
||||
|
||||
// To reduce differences between values returned by different kinds
|
||||
// of browsers, we round `posX` and `posY`.
|
||||
//
|
||||
// IE10: `posX` and `posY` - float.
|
||||
// Chrome, FF: `posX` and `posY` - integers.
|
||||
result = '[' + Math.round(posX) + ',' + Math.round(posY) + ']';
|
||||
this.crossEl.css({
|
||||
left: posX - 15,
|
||||
top: posY - 15,
|
||||
visibility: "visible",
|
||||
});
|
||||
|
||||
this.crossEl.css({
|
||||
left: posX - 15,
|
||||
top: posY - 15,
|
||||
visibility: 'visible'
|
||||
});
|
||||
|
||||
this.inputEl.val(result);
|
||||
}
|
||||
}).call(this, window.jQuery);
|
||||
this.inputEl.val(result);
|
||||
}
|
||||
}.call(this, window.jQuery);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,119 +1,121 @@
|
||||
// eslint-disable-next-line no-shadow-restricted-names
|
||||
(function(undefined) {
|
||||
'use strict';
|
||||
(function (undefined) {
|
||||
"use strict";
|
||||
|
||||
// [module Collapsible]
|
||||
//
|
||||
// [description]
|
||||
// Set of library functions that provide a simple way to add
|
||||
// collapsible functionality to elements.
|
||||
this.Collapsible = {
|
||||
setCollapsibles: setCollapsibles,
|
||||
toggleFull: toggleFull,
|
||||
toggleHint: toggleHint
|
||||
};
|
||||
// [module Collapsible]
|
||||
//
|
||||
// [description]
|
||||
// Set of library functions that provide a simple way to add
|
||||
// collapsible functionality to elements.
|
||||
this.Collapsible = {
|
||||
setCollapsibles: setCollapsibles,
|
||||
toggleFull: toggleFull,
|
||||
toggleHint: toggleHint,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-useless-return
|
||||
return;
|
||||
// eslint-disable-next-line no-useless-return
|
||||
return;
|
||||
|
||||
// [function setCollapsibles]
|
||||
//
|
||||
// [description]
|
||||
// Scan element's content for generic collapsible containers.
|
||||
//
|
||||
// [params]
|
||||
// el: container
|
||||
function setCollapsibles(el) {
|
||||
var linkBottom, linkTop, short_custom;
|
||||
// [function setCollapsibles]
|
||||
//
|
||||
// [description]
|
||||
// Scan element's content for generic collapsible containers.
|
||||
//
|
||||
// [params]
|
||||
// el: container
|
||||
function setCollapsibles(el) {
|
||||
var linkBottom, linkTop, short_custom;
|
||||
|
||||
linkTop = '<a href="#" class="full full-top">See full output</a>';
|
||||
linkBottom = '<a href="#" class="full full-bottom">See full output</a>';
|
||||
linkTop = '<a href="#" class="full full-top">See full output</a>';
|
||||
linkBottom = '<a href="#" class="full full-bottom">See full output</a>';
|
||||
|
||||
// Standard longform + shortfom pattern.
|
||||
el.find('.longform').hide();
|
||||
el.find('.shortform').append(linkTop, linkBottom); // xss-lint: disable=javascript-jquery-append
|
||||
// Standard longform + shortfom pattern.
|
||||
el.find(".longform").hide();
|
||||
el.find(".shortform").append(linkTop, linkBottom); // xss-lint: disable=javascript-jquery-append
|
||||
|
||||
// Custom longform + shortform text pattern.
|
||||
short_custom = el.find('.shortform-custom');
|
||||
// Custom longform + shortform text pattern.
|
||||
short_custom = el.find(".shortform-custom");
|
||||
|
||||
// Set up each one individually.
|
||||
short_custom.each(function(index, elt) {
|
||||
var close_text, open_text;
|
||||
// Set up each one individually.
|
||||
short_custom.each(function (index, elt) {
|
||||
var close_text, open_text;
|
||||
|
||||
open_text = $(elt).data('open-text');
|
||||
close_text = $(elt).data('close-text');
|
||||
edx.HtmlUtils.append(
|
||||
$(elt),
|
||||
edx.HtmlUtils.joinHtml(
|
||||
edx.HtmlUtils.HTML("<a href='#' class='full-custom'>"),
|
||||
gettext(open_text),
|
||||
edx.HtmlUtils.HTML('</a>')
|
||||
)
|
||||
);
|
||||
open_text = $(elt).data("open-text");
|
||||
close_text = $(elt).data("close-text");
|
||||
edx.HtmlUtils.append(
|
||||
$(elt),
|
||||
edx.HtmlUtils.joinHtml(
|
||||
edx.HtmlUtils.HTML("<a href='#' class='full-custom'>"),
|
||||
gettext(open_text),
|
||||
edx.HtmlUtils.HTML("</a>"),
|
||||
),
|
||||
);
|
||||
|
||||
$(elt).find('.full-custom').click(function(event) {
|
||||
Collapsible.toggleFull(event, open_text, close_text);
|
||||
});
|
||||
$(elt)
|
||||
.find(".full-custom")
|
||||
.click(function (event) {
|
||||
Collapsible.toggleFull(event, open_text, close_text);
|
||||
});
|
||||
});
|
||||
|
||||
// Collapsible pattern.
|
||||
el.find('.collapsible header + section').hide();
|
||||
// Collapsible pattern.
|
||||
el.find(".collapsible header + section").hide();
|
||||
|
||||
// Set up triggers.
|
||||
el.find('.full').click(function(event) {
|
||||
Collapsible.toggleFull(event, 'See full output', 'Hide output');
|
||||
});
|
||||
el.find('.collapsible header a').click(Collapsible.toggleHint);
|
||||
// Set up triggers.
|
||||
el.find(".full").click(function (event) {
|
||||
Collapsible.toggleFull(event, "See full output", "Hide output");
|
||||
});
|
||||
el.find(".collapsible header a").click(Collapsible.toggleHint);
|
||||
}
|
||||
|
||||
// [function toggleFull]
|
||||
//
|
||||
// [description]
|
||||
// Toggle the display of full text for a collapsible element.
|
||||
//
|
||||
// [params]
|
||||
// event: jQuery event object associated with the event that
|
||||
// triggered this callback function.
|
||||
// open_text: text that should be displayed when the collapsible
|
||||
// is open.
|
||||
// close_text: text that should be displayed when the collapsible
|
||||
// is closed.
|
||||
function toggleFull(event, open_text, close_text) {
|
||||
var $el, new_text, parent;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
parent = $(event.target).parent();
|
||||
parent.siblings().slideToggle();
|
||||
parent.parent().toggleClass("open");
|
||||
|
||||
if ($(event.target).text() === open_text) {
|
||||
new_text = close_text;
|
||||
} else {
|
||||
new_text = open_text;
|
||||
}
|
||||
|
||||
// [function toggleFull]
|
||||
//
|
||||
// [description]
|
||||
// Toggle the display of full text for a collapsible element.
|
||||
//
|
||||
// [params]
|
||||
// event: jQuery event object associated with the event that
|
||||
// triggered this callback function.
|
||||
// open_text: text that should be displayed when the collapsible
|
||||
// is open.
|
||||
// close_text: text that should be displayed when the collapsible
|
||||
// is closed.
|
||||
function toggleFull(event, open_text, close_text) {
|
||||
var $el, new_text, parent;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
parent = $(event.target).parent();
|
||||
parent.siblings().slideToggle();
|
||||
parent.parent().toggleClass('open');
|
||||
|
||||
if ($(event.target).text() === open_text) {
|
||||
new_text = close_text;
|
||||
} else {
|
||||
new_text = open_text;
|
||||
}
|
||||
|
||||
if ($(event.target).hasClass('full')) {
|
||||
$el = parent.find('.full');
|
||||
} else {
|
||||
$el = $(event.target);
|
||||
}
|
||||
|
||||
$el.text(new_text);
|
||||
if ($(event.target).hasClass("full")) {
|
||||
$el = parent.find(".full");
|
||||
} else {
|
||||
$el = $(event.target);
|
||||
}
|
||||
|
||||
// [function toggleHint]
|
||||
//
|
||||
// [description]
|
||||
// Toggle the collapsible open to show the hint.
|
||||
//
|
||||
// [params]
|
||||
// event: jQuery event object associated with the event that
|
||||
// triggered this callback function.
|
||||
function toggleHint(event) {
|
||||
event.preventDefault();
|
||||
$el.text(new_text);
|
||||
}
|
||||
|
||||
$(event.target).parent().siblings().slideToggle();
|
||||
$(event.target).parent().parent().toggleClass('open');
|
||||
}
|
||||
// [function toggleHint]
|
||||
//
|
||||
// [description]
|
||||
// Toggle the collapsible open to show the hint.
|
||||
//
|
||||
// [params]
|
||||
// event: jQuery event object associated with the event that
|
||||
// triggered this callback function.
|
||||
function toggleHint(event) {
|
||||
event.preventDefault();
|
||||
|
||||
$(event.target).parent().siblings().slideToggle();
|
||||
$(event.target).parent().parent().toggleClass("open");
|
||||
}
|
||||
}).call(this);
|
||||
|
||||
@@ -1,90 +1,97 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
this.JavascriptLoader = (function() {
|
||||
function JavascriptLoader() {
|
||||
this.JavascriptLoader = (function () {
|
||||
function JavascriptLoader() {}
|
||||
|
||||
/**
|
||||
* Set of library functions that provide common interface for javascript loading
|
||||
* for all module types. All functionality provided by JavascriptLoader should take
|
||||
* place at module scope, i.e. don't run jQuery over entire page.
|
||||
*
|
||||
* executeModuleScripts:
|
||||
* Scan the module ('el') for "script_placeholder"s, then:
|
||||
*
|
||||
* 1) Fetch each script from server
|
||||
* 2) Explicitly attach the script to the <head> of document
|
||||
* 3) Explicitly wait for each script to be loaded
|
||||
* 4) Return to callback function when all scripts loaded
|
||||
*/
|
||||
JavascriptLoader.executeModuleScripts = function (el, callback) {
|
||||
var callbackCalled, completed, completionHandlerGenerator, loaded, placeholders;
|
||||
if (!callback) {
|
||||
callback = null; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
placeholders = el.find(".script_placeholder");
|
||||
if (placeholders.length === 0) {
|
||||
if (callback !== null) {
|
||||
callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of library functions that provide common interface for javascript loading
|
||||
* for all module types. All functionality provided by JavascriptLoader should take
|
||||
* place at module scope, i.e. don't run jQuery over entire page.
|
||||
*
|
||||
* executeModuleScripts:
|
||||
* Scan the module ('el') for "script_placeholder"s, then:
|
||||
*
|
||||
* 1) Fetch each script from server
|
||||
* 2) Explicitly attach the script to the <head> of document
|
||||
* 3) Explicitly wait for each script to be loaded
|
||||
* 4) Return to callback function when all scripts loaded
|
||||
*/
|
||||
JavascriptLoader.executeModuleScripts = function(el, callback) {
|
||||
var callbackCalled, completed, completionHandlerGenerator, loaded, placeholders;
|
||||
if (!callback) {
|
||||
callback = null; // eslint-disable-line no-param-reassign
|
||||
return [];
|
||||
}
|
||||
// TODO: Verify the execution order of multiple placeholders
|
||||
completed = (function () {
|
||||
var i, ref, results;
|
||||
results = [];
|
||||
for (i = 1, ref = placeholders.length; ref >= 1 ? i <= ref : i >= ref; ref >= 1 ? ++i : --i) {
|
||||
results.push(false);
|
||||
}
|
||||
return results;
|
||||
})();
|
||||
callbackCalled = false;
|
||||
completionHandlerGenerator = function (index) {
|
||||
return function () {
|
||||
var allComplete, flag, i, len;
|
||||
allComplete = true;
|
||||
completed[index] = true;
|
||||
for (i = 0, len = completed.length; i < len; i++) {
|
||||
flag = completed[i];
|
||||
if (!flag) {
|
||||
allComplete = false;
|
||||
break;
|
||||
}
|
||||
placeholders = el.find('.script_placeholder');
|
||||
if (placeholders.length === 0) {
|
||||
if (callback !== null) {
|
||||
callback();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
if (allComplete && !callbackCalled) {
|
||||
callbackCalled = true;
|
||||
if (callback !== null) {
|
||||
return callback();
|
||||
}
|
||||
// TODO: Verify the execution order of multiple placeholders
|
||||
completed = (function() {
|
||||
var i, ref, results;
|
||||
results = [];
|
||||
for (i = 1, ref = placeholders.length; ref >= 1 ? i <= ref : i >= ref; ref >= 1 ? ++i : --i) {
|
||||
results.push(false);
|
||||
}
|
||||
return results;
|
||||
}());
|
||||
callbackCalled = false;
|
||||
completionHandlerGenerator = function(index) {
|
||||
return function() {
|
||||
var allComplete, flag, i, len;
|
||||
allComplete = true;
|
||||
completed[index] = true;
|
||||
for (i = 0, len = completed.length; i < len; i++) {
|
||||
flag = completed[i];
|
||||
if (!flag) {
|
||||
allComplete = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allComplete && !callbackCalled) {
|
||||
callbackCalled = true;
|
||||
if (callback !== null) {
|
||||
return callback();
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
};
|
||||
// Keep a map of what sources we're loaded from, and don't do it twice.
|
||||
loaded = {};
|
||||
return placeholders.each(function(index, placeholder) {
|
||||
var s, src;
|
||||
// TODO: Check if the script already exists in DOM. If so, (1) copy it
|
||||
// into memory; (2) delete the DOM script element; (3) reappend it.
|
||||
// This would prevent memory bloat and save a network request.
|
||||
src = $(placeholder).attr('data-src');
|
||||
if (!(src in loaded)) {
|
||||
loaded[src] = true;
|
||||
s = document.createElement('script');
|
||||
s.setAttribute('src', src);
|
||||
s.setAttribute('type', 'text/javascript');
|
||||
s.onload = completionHandlerGenerator(index);
|
||||
// Need to use the DOM elements directly or the scripts won't execute properly.
|
||||
$('head')[0].appendChild(s);
|
||||
} else {
|
||||
// just call the completion callback directly, without reloading the file
|
||||
completionHandlerGenerator(index)();
|
||||
}
|
||||
return $(placeholder).remove();
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
};
|
||||
// Keep a map of what sources we're loaded from, and don't do it twice.
|
||||
loaded = {};
|
||||
return placeholders.each(function (index, placeholder) {
|
||||
var s, src, src_escaped;
|
||||
// TODO: Check if the script already exists in DOM. If so, (1) copy it
|
||||
// into memory; (2) delete the DOM script element; (3) reappend it.
|
||||
// This would prevent memory bloat and save a network request.
|
||||
src = $(placeholder).attr("data-src");
|
||||
if (/^\s*(javascript|data|vbscript):/i.test(src)) {
|
||||
console.warn("Blocked unsafe script source:", src);
|
||||
completionHandlerGenerator(index)();
|
||||
return $(placeholder).remove();
|
||||
}
|
||||
src_escaped = String(src || "")
|
||||
.replace(/</g, "%3C")
|
||||
.replace(/>/g, "%3E");
|
||||
if (!(src_escaped in loaded)) {
|
||||
loaded[src_escaped] = true;
|
||||
s = document.createElement("script");
|
||||
s.setAttribute("src", src_escaped);
|
||||
s.setAttribute("type", "text/javascript");
|
||||
s.onload = completionHandlerGenerator(index);
|
||||
// Need to use the DOM elements directly or the scripts won't execute properly.
|
||||
$("head")[0].appendChild(s);
|
||||
} else {
|
||||
// just call the completion callback directly, without reloading the file
|
||||
completionHandlerGenerator(index)();
|
||||
}
|
||||
return $(placeholder).remove();
|
||||
});
|
||||
};
|
||||
|
||||
return JavascriptLoader;
|
||||
}());
|
||||
return JavascriptLoader;
|
||||
})();
|
||||
}).call(this);
|
||||
|
||||
@@ -4,815 +4,902 @@
|
||||
// failed, but only in Jenkins, indicating browser-specific behavior.
|
||||
/* eslint no-useless-escape: 0 */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var hasPropsHelper = {}.hasOwnProperty,
|
||||
extendsHelper = function(child, parent) {
|
||||
// This helper method was generated by CoffeeScript. Suppressing eslint warnings.
|
||||
var key;
|
||||
for (key in parent) { // eslint-disable-line no-restricted-syntax
|
||||
if (hasPropsHelper.call(parent, key)) {
|
||||
child[key] = parent[key]; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
}
|
||||
function ctor() {
|
||||
this.constructor = child;
|
||||
}
|
||||
|
||||
ctor.prototype = parent.prototype;
|
||||
child.prototype = new ctor(); // eslint-disable-line no-param-reassign
|
||||
child.__super__ = parent.prototype; // eslint-disable-line no-param-reassign, no-underscore-dangle
|
||||
return child;
|
||||
};
|
||||
|
||||
this.MarkdownEditingDescriptor = (function(_super) {
|
||||
// The style of these declarations come from CoffeeScript. Rather than rewriting them,
|
||||
// the eslint warnings are being suppressed.
|
||||
extendsHelper(MarkdownEditingDescriptor, _super); // eslint-disable-line no-use-before-define
|
||||
|
||||
MarkdownEditingDescriptor.multipleChoiceTemplate = '( ) ' // eslint-disable-line no-use-before-define
|
||||
+ (gettext('incorrect')) + '\n( ) ' + (gettext('incorrect')) + '\n(x) ' + (gettext('correct')) + '\n';
|
||||
|
||||
MarkdownEditingDescriptor.checkboxChoiceTemplate = '[x] ' // eslint-disable-line no-use-before-define
|
||||
+ (gettext('correct')) + '\n[ ] incorrect\n[x] correct\n';
|
||||
|
||||
MarkdownEditingDescriptor.stringInputTemplate = '= ' // eslint-disable-line no-use-before-define
|
||||
+ (gettext('answer')) + '\n';
|
||||
|
||||
MarkdownEditingDescriptor.numberInputTemplate = '= ' // eslint-disable-line no-use-before-define
|
||||
+ (gettext('answer')) + ' +- 0.001%\n';
|
||||
|
||||
MarkdownEditingDescriptor.selectTemplate = '[[' // eslint-disable-line no-use-before-define
|
||||
+ (gettext('incorrect')) + ', (' + (gettext('correct')) + '), ' + (gettext('incorrect')) + ']]\n';
|
||||
|
||||
MarkdownEditingDescriptor.headerTemplate = '' // eslint-disable-line no-use-before-define
|
||||
+ (gettext('Header')) + '\n=====\n';
|
||||
|
||||
MarkdownEditingDescriptor.explanationTemplate = '[explanation]\n' // eslint-disable-line no-use-before-define
|
||||
+ (gettext('Short explanation')) + '\n[explanation]\n';
|
||||
|
||||
function MarkdownEditingDescriptor(element) {
|
||||
var that = this;
|
||||
this.onToolbarButton = function() {
|
||||
return MarkdownEditingDescriptor.prototype.onToolbarButton.apply(that, arguments);
|
||||
};
|
||||
this.onShowXMLButton = function() {
|
||||
return MarkdownEditingDescriptor.prototype.onShowXMLButton.apply(that, arguments);
|
||||
};
|
||||
this.element = element;
|
||||
if ($('.markdown-box', this.element).length !== 0) {
|
||||
this.markdown_editor = CodeMirror.fromTextArea($('.markdown-box', element)[0], {
|
||||
lineWrapping: true,
|
||||
mode: null
|
||||
});
|
||||
this.setCurrentEditor(this.markdown_editor);
|
||||
// Add listeners for toolbar buttons (only present for markdown editor)
|
||||
this.element.on('click', '.xml-tab', this.onShowXMLButton);
|
||||
this.element.on('click', '.format-buttons button', this.onToolbarButton);
|
||||
// Hide the XML text area
|
||||
$(this.element.find('.xml-box')).hide();
|
||||
} else {
|
||||
this.createXMLEditor();
|
||||
}
|
||||
var hasPropsHelper = {}.hasOwnProperty,
|
||||
extendsHelper = function (child, parent) {
|
||||
// This helper method was generated by CoffeeScript. Suppressing eslint warnings.
|
||||
var key;
|
||||
for (key in parent) {
|
||||
// eslint-disable-line no-restricted-syntax
|
||||
if (hasPropsHelper.call(parent, key)) {
|
||||
child[key] = parent[key]; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
}
|
||||
function ctor() {
|
||||
this.constructor = child;
|
||||
}
|
||||
|
||||
/*
|
||||
ctor.prototype = parent.prototype;
|
||||
child.prototype = new ctor(); // eslint-disable-line no-param-reassign
|
||||
child.__super__ = parent.prototype; // eslint-disable-line no-param-reassign, no-underscore-dangle
|
||||
return child;
|
||||
};
|
||||
|
||||
this.MarkdownEditingDescriptor = (function (_super) {
|
||||
// The style of these declarations come from CoffeeScript. Rather than rewriting them,
|
||||
// the eslint warnings are being suppressed.
|
||||
extendsHelper(MarkdownEditingDescriptor, _super); // eslint-disable-line no-use-before-define
|
||||
|
||||
MarkdownEditingDescriptor.multipleChoiceTemplate =
|
||||
"( ) " + // eslint-disable-line no-use-before-define
|
||||
gettext("incorrect") +
|
||||
"\n( ) " +
|
||||
gettext("incorrect") +
|
||||
"\n(x) " +
|
||||
gettext("correct") +
|
||||
"\n";
|
||||
|
||||
MarkdownEditingDescriptor.checkboxChoiceTemplate =
|
||||
"[x] " + // eslint-disable-line no-use-before-define
|
||||
gettext("correct") +
|
||||
"\n[ ] incorrect\n[x] correct\n";
|
||||
|
||||
MarkdownEditingDescriptor.stringInputTemplate =
|
||||
"= " + // eslint-disable-line no-use-before-define
|
||||
gettext("answer") +
|
||||
"\n";
|
||||
|
||||
MarkdownEditingDescriptor.numberInputTemplate =
|
||||
"= " + // eslint-disable-line no-use-before-define
|
||||
gettext("answer") +
|
||||
" +- 0.001%\n";
|
||||
|
||||
MarkdownEditingDescriptor.selectTemplate =
|
||||
"[[" + // eslint-disable-line no-use-before-define
|
||||
gettext("incorrect") +
|
||||
", (" +
|
||||
gettext("correct") +
|
||||
"), " +
|
||||
gettext("incorrect") +
|
||||
"]]\n";
|
||||
|
||||
MarkdownEditingDescriptor.headerTemplate =
|
||||
"" + // eslint-disable-line no-use-before-define
|
||||
gettext("Header") +
|
||||
"\n=====\n";
|
||||
|
||||
MarkdownEditingDescriptor.explanationTemplate =
|
||||
"[explanation]\n" + // eslint-disable-line no-use-before-define
|
||||
gettext("Short explanation") +
|
||||
"\n[explanation]\n";
|
||||
|
||||
function MarkdownEditingDescriptor(element) {
|
||||
var that = this;
|
||||
this.onToolbarButton = function () {
|
||||
return MarkdownEditingDescriptor.prototype.onToolbarButton.apply(that, arguments);
|
||||
};
|
||||
this.onShowXMLButton = function () {
|
||||
return MarkdownEditingDescriptor.prototype.onShowXMLButton.apply(that, arguments);
|
||||
};
|
||||
this.element = element;
|
||||
if ($(".markdown-box", this.element).length !== 0) {
|
||||
this.markdown_editor = CodeMirror.fromTextArea($(".markdown-box", element)[0], {
|
||||
lineWrapping: true,
|
||||
mode: null,
|
||||
});
|
||||
this.setCurrentEditor(this.markdown_editor);
|
||||
// Add listeners for toolbar buttons (only present for markdown editor)
|
||||
this.element.on("click", ".xml-tab", this.onShowXMLButton);
|
||||
this.element.on("click", ".format-buttons button", this.onToolbarButton);
|
||||
// Hide the XML text area
|
||||
$(this.element.find(".xml-box")).hide();
|
||||
} else {
|
||||
this.createXMLEditor();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Creates the XML Editor and sets it as the current editor. If text is passed in,
|
||||
it will replace the text present in the HTML template.
|
||||
|
||||
text: optional argument to override the text passed in via the HTML template
|
||||
*/
|
||||
MarkdownEditingDescriptor.prototype.createXMLEditor = function(text) {
|
||||
this.xml_editor = CodeMirror.fromTextArea($('.xml-box', this.element)[0], {
|
||||
mode: 'xml',
|
||||
lineNumbers: true,
|
||||
lineWrapping: true
|
||||
});
|
||||
if (text) {
|
||||
this.xml_editor.setValue(text);
|
||||
}
|
||||
this.setCurrentEditor(this.xml_editor);
|
||||
$(this.xml_editor.getWrapperElement()).toggleClass('CodeMirror-advanced');
|
||||
// Need to refresh to get line numbers to display properly.
|
||||
this.xml_editor.refresh();
|
||||
};
|
||||
MarkdownEditingDescriptor.prototype.createXMLEditor = function (text) {
|
||||
this.xml_editor = CodeMirror.fromTextArea($(".xml-box", this.element)[0], {
|
||||
mode: "xml",
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
});
|
||||
if (text) {
|
||||
this.xml_editor.setValue(text);
|
||||
}
|
||||
this.setCurrentEditor(this.xml_editor);
|
||||
$(this.xml_editor.getWrapperElement()).toggleClass("CodeMirror-advanced");
|
||||
// Need to refresh to get line numbers to display properly.
|
||||
this.xml_editor.refresh();
|
||||
};
|
||||
|
||||
/*
|
||||
/*
|
||||
User has clicked to show the XML editor. Before XML editor is swapped in,
|
||||
the user will need to confirm the one-way conversion.
|
||||
*/
|
||||
MarkdownEditingDescriptor.prototype.onShowXMLButton = function(e) {
|
||||
e.preventDefault();
|
||||
if (this.confirmConversionToXml()) {
|
||||
this.createXMLEditor(MarkdownEditingDescriptor.markdownToXml(this.markdown_editor.getValue()));
|
||||
this.xml_editor.setCursor(0);
|
||||
// Hide markdown-specific toolbar buttons
|
||||
$(this.element.find('.editor-bar')).hide();
|
||||
}
|
||||
};
|
||||
MarkdownEditingDescriptor.prototype.onShowXMLButton = function (e) {
|
||||
e.preventDefault();
|
||||
if (this.confirmConversionToXml()) {
|
||||
this.createXMLEditor(MarkdownEditingDescriptor.markdownToXml(this.markdown_editor.getValue()));
|
||||
this.xml_editor.setCursor(0);
|
||||
// Hide markdown-specific toolbar buttons
|
||||
$(this.element.find(".editor-bar")).hide();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
/*
|
||||
Have the user confirm the one-way conversion to XML.
|
||||
Returns true if the user clicked OK, else false.
|
||||
*/
|
||||
MarkdownEditingDescriptor.prototype.confirmConversionToXml = function() {
|
||||
return confirm(gettext('If you use the Advanced Editor, this problem will be converted to XML and you will not be able to return to the Simple Editor Interface.\n\nProceed to the Advanced Editor and convert this problem to XML?')); // eslint-disable-line max-len, no-alert
|
||||
};
|
||||
MarkdownEditingDescriptor.prototype.confirmConversionToXml = function () {
|
||||
return confirm(
|
||||
gettext(
|
||||
"If you use the Advanced Editor, this problem will be converted to XML and you will not be able to return to the Simple Editor Interface.\n\nProceed to the Advanced Editor and convert this problem to XML?",
|
||||
),
|
||||
); // eslint-disable-line max-len, no-alert
|
||||
};
|
||||
|
||||
/*
|
||||
/*
|
||||
Event listener for toolbar buttons (only possible when markdown editor is visible).
|
||||
*/
|
||||
MarkdownEditingDescriptor.prototype.onToolbarButton = function(e) {
|
||||
var revisedSelection, selection;
|
||||
e.preventDefault();
|
||||
selection = this.markdown_editor.getSelection();
|
||||
revisedSelection = null;
|
||||
switch ($(e.currentTarget).attr('class')) {
|
||||
case 'multiple-choice-button':
|
||||
revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice(selection);
|
||||
break;
|
||||
case 'string-button':
|
||||
revisedSelection = MarkdownEditingDescriptor.insertStringInput(selection);
|
||||
break;
|
||||
case 'number-button':
|
||||
revisedSelection = MarkdownEditingDescriptor.insertNumberInput(selection);
|
||||
break;
|
||||
case 'checks-button':
|
||||
revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice(selection);
|
||||
break;
|
||||
case 'dropdown-button':
|
||||
revisedSelection = MarkdownEditingDescriptor.insertSelect(selection);
|
||||
break;
|
||||
case 'header-button':
|
||||
revisedSelection = MarkdownEditingDescriptor.insertHeader(selection);
|
||||
break;
|
||||
case 'explanation-button':
|
||||
revisedSelection = MarkdownEditingDescriptor.insertExplanation(selection);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (revisedSelection !== null) {
|
||||
this.markdown_editor.replaceSelection(revisedSelection);
|
||||
this.markdown_editor.focus();
|
||||
}
|
||||
};
|
||||
MarkdownEditingDescriptor.prototype.onToolbarButton = function (e) {
|
||||
var revisedSelection, selection;
|
||||
e.preventDefault();
|
||||
selection = this.markdown_editor.getSelection();
|
||||
revisedSelection = null;
|
||||
switch ($(e.currentTarget).attr("class")) {
|
||||
case "multiple-choice-button":
|
||||
revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice(selection);
|
||||
break;
|
||||
case "string-button":
|
||||
revisedSelection = MarkdownEditingDescriptor.insertStringInput(selection);
|
||||
break;
|
||||
case "number-button":
|
||||
revisedSelection = MarkdownEditingDescriptor.insertNumberInput(selection);
|
||||
break;
|
||||
case "checks-button":
|
||||
revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice(selection);
|
||||
break;
|
||||
case "dropdown-button":
|
||||
revisedSelection = MarkdownEditingDescriptor.insertSelect(selection);
|
||||
break;
|
||||
case "header-button":
|
||||
revisedSelection = MarkdownEditingDescriptor.insertHeader(selection);
|
||||
break;
|
||||
case "explanation-button":
|
||||
revisedSelection = MarkdownEditingDescriptor.insertExplanation(selection);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (revisedSelection !== null) {
|
||||
this.markdown_editor.replaceSelection(revisedSelection);
|
||||
this.markdown_editor.focus();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
/*
|
||||
Stores the current editor and hides the one that is not displayed.
|
||||
*/
|
||||
MarkdownEditingDescriptor.prototype.setCurrentEditor = function(editor) {
|
||||
if (this.current_editor) {
|
||||
$(this.current_editor.getWrapperElement()).hide();
|
||||
}
|
||||
this.current_editor = editor;
|
||||
$(this.current_editor.getWrapperElement()).show();
|
||||
return $(this.current_editor).focus();
|
||||
};
|
||||
MarkdownEditingDescriptor.prototype.setCurrentEditor = function (editor) {
|
||||
if (this.current_editor) {
|
||||
$(this.current_editor.getWrapperElement()).hide();
|
||||
}
|
||||
this.current_editor = editor;
|
||||
$(this.current_editor.getWrapperElement()).show();
|
||||
return $(this.current_editor).focus();
|
||||
};
|
||||
|
||||
/*
|
||||
/*
|
||||
Called when save is called. Listeners are unregistered because editing the block again will
|
||||
result in a new instance of the descriptor. Note that this is NOT the case for cancel--
|
||||
when cancel is called the instance of the descriptor is reused if edit is selected again.
|
||||
*/
|
||||
MarkdownEditingDescriptor.prototype.save = function() {
|
||||
this.element.off('click', '.xml-tab', this.changeEditor);
|
||||
this.element.off('click', '.format-buttons button', this.onToolbarButton);
|
||||
if (this.current_editor === this.markdown_editor) {
|
||||
return {
|
||||
data: MarkdownEditingDescriptor.markdownToXml(this.markdown_editor.getValue()),
|
||||
metadata: {
|
||||
markdown: this.markdown_editor.getValue()
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
data: this.xml_editor.getValue(),
|
||||
nullout: ['markdown']
|
||||
};
|
||||
MarkdownEditingDescriptor.prototype.save = function () {
|
||||
this.element.off("click", ".xml-tab", this.changeEditor);
|
||||
this.element.off("click", ".format-buttons button", this.onToolbarButton);
|
||||
if (this.current_editor === this.markdown_editor) {
|
||||
return {
|
||||
data: MarkdownEditingDescriptor.markdownToXml(this.markdown_editor.getValue()),
|
||||
metadata: {
|
||||
markdown: this.markdown_editor.getValue(),
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
data: this.xml_editor.getValue(),
|
||||
nullout: ["markdown"],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertMultipleChoice = function (selectedText) {
|
||||
return MarkdownEditingDescriptor.insertGenericChoice(
|
||||
selectedText,
|
||||
"(",
|
||||
")",
|
||||
MarkdownEditingDescriptor.multipleChoiceTemplate,
|
||||
);
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertCheckboxChoice = function (selectedText) {
|
||||
return MarkdownEditingDescriptor.insertGenericChoice(
|
||||
selectedText,
|
||||
"[",
|
||||
"]",
|
||||
MarkdownEditingDescriptor.checkboxChoiceTemplate,
|
||||
);
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertGenericChoice = function (selectedText, choiceStart, choiceEnd, template) {
|
||||
var cleanSelectedText, line, lines, revisedLines, i, len;
|
||||
if (selectedText.length > 0) {
|
||||
// Replace adjacent newlines with a single newline, strip any trailing newline
|
||||
cleanSelectedText = selectedText.replace(/\n+/g, "\n").replace(/\n$/, "");
|
||||
lines = cleanSelectedText.split("\n");
|
||||
revisedLines = "";
|
||||
for (i = 0, len = lines.length; i < len; i++) {
|
||||
line = lines[i];
|
||||
revisedLines += choiceStart;
|
||||
// a stand alone x before other text implies that this option is "correct"
|
||||
if (/^\s*x\s+(\S)/i.test(line)) {
|
||||
// Remove the x and any initial whitespace as long as there's more text on the line
|
||||
line = line.replace(/^\s*x\s+(\S)/i, "$1");
|
||||
revisedLines += "x";
|
||||
} else {
|
||||
revisedLines += " ";
|
||||
}
|
||||
revisedLines += choiceEnd + " " + line + "\n";
|
||||
}
|
||||
return revisedLines;
|
||||
} else {
|
||||
return template;
|
||||
}
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertStringInput = function (selectedText) {
|
||||
return MarkdownEditingDescriptor.insertGenericInput(
|
||||
selectedText,
|
||||
"= ",
|
||||
"",
|
||||
MarkdownEditingDescriptor.stringInputTemplate,
|
||||
);
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertNumberInput = function (selectedText) {
|
||||
return MarkdownEditingDescriptor.insertGenericInput(
|
||||
selectedText,
|
||||
"= ",
|
||||
"",
|
||||
MarkdownEditingDescriptor.numberInputTemplate,
|
||||
);
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertSelect = function (selectedText) {
|
||||
return MarkdownEditingDescriptor.insertGenericInput(
|
||||
selectedText,
|
||||
"[[",
|
||||
"]]",
|
||||
MarkdownEditingDescriptor.selectTemplate,
|
||||
);
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertHeader = function (selectedText) {
|
||||
return MarkdownEditingDescriptor.insertGenericInput(
|
||||
selectedText,
|
||||
"",
|
||||
"\n====\n",
|
||||
MarkdownEditingDescriptor.headerTemplate,
|
||||
);
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertExplanation = function (selectedText) {
|
||||
return MarkdownEditingDescriptor.insertGenericInput(
|
||||
selectedText,
|
||||
"[explanation]\n",
|
||||
"\n[explanation]",
|
||||
MarkdownEditingDescriptor.explanationTemplate,
|
||||
);
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertGenericInput = function (selectedText, lineStart, lineEnd, template) {
|
||||
if (selectedText.length > 0) {
|
||||
return lineStart + selectedText + lineEnd;
|
||||
} else {
|
||||
return template;
|
||||
}
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.markdownToXml = function (markdown) {
|
||||
var demandHintTags = [],
|
||||
finalDemandHints,
|
||||
finalXml,
|
||||
responseTypesMarkdown,
|
||||
responseTypesXML,
|
||||
toXml;
|
||||
toXml = function (partialMarkdown) {
|
||||
var xml = partialMarkdown,
|
||||
i,
|
||||
splits,
|
||||
makeParagraph,
|
||||
serializer,
|
||||
responseType,
|
||||
$xml,
|
||||
responseTypesSelector,
|
||||
inputtype,
|
||||
beforeInputtype,
|
||||
extractHint,
|
||||
demandhints;
|
||||
var responseTypes = [
|
||||
"optionresponse",
|
||||
"multiplechoiceresponse",
|
||||
"stringresponse",
|
||||
"numericalresponse",
|
||||
"choiceresponse",
|
||||
];
|
||||
|
||||
// fix DOS \r\n line endings to look like \n
|
||||
xml = xml.replace(/\r\n/g, "\n");
|
||||
|
||||
// replace headers
|
||||
xml = xml.replace(/(^.*?$)(?=\n\=\=+$)/gm, '<h3 class="hd hd-2 problem-header">$1</h3>');
|
||||
xml = xml.replace(/\n^\=\=+$/gm, "");
|
||||
|
||||
// extract question and description(optional)
|
||||
// >>question||description<< converts to
|
||||
// <label>question</label> <description>description</description>
|
||||
xml = xml.replace(/>>([^]+?)<</gm, function (match, questionText) {
|
||||
var result = questionText.split("||"),
|
||||
label = "<label>" + result[0] + "</label>\n"; // xss-lint: disable=javascript-concat-html
|
||||
|
||||
// don't add empty <description> tag
|
||||
if (result.length === 1 || !result[1]) {
|
||||
return label;
|
||||
}
|
||||
|
||||
return label + "<description>" + result[1] + "</description>\n"; // xss-lint: disable=javascript-concat-html
|
||||
});
|
||||
|
||||
// Pull out demand hints, || a hint ||
|
||||
demandhints = "";
|
||||
xml = xml.replace(/(^\s*\|\|.*?\|\|\s*$\n?)+/gm, function (match) {
|
||||
// $\n
|
||||
var inner,
|
||||
options = match.split("\n");
|
||||
for (i = 0; i < options.length; i += 1) {
|
||||
inner = /\s*\|\|(.*?)\|\|/.exec(options[i]);
|
||||
if (inner) {
|
||||
demandhints += " <hint>" + inner[1].trim() + "</hint>\n"; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
};
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
MarkdownEditingDescriptor.insertMultipleChoice = function(selectedText) {
|
||||
return MarkdownEditingDescriptor.insertGenericChoice(selectedText, '(', ')',
|
||||
MarkdownEditingDescriptor.multipleChoiceTemplate
|
||||
);
|
||||
};
|
||||
// replace \n+whitespace within extended hint {{ .. }}, by a space, so the whole
|
||||
// hint sits on one line.
|
||||
// This is the one instance of {{ ... }} matching that permits \n
|
||||
xml = xml.replace(/{{(.|\n)*?}}/gm, function (match) {
|
||||
return match.replace(/\r?\n( |\t)*/g, " ");
|
||||
});
|
||||
|
||||
MarkdownEditingDescriptor.insertCheckboxChoice = function(selectedText) {
|
||||
return MarkdownEditingDescriptor.insertGenericChoice(selectedText, '[', ']',
|
||||
MarkdownEditingDescriptor.checkboxChoiceTemplate
|
||||
);
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertGenericChoice = function(selectedText, choiceStart, choiceEnd, template) {
|
||||
var cleanSelectedText, line, lines, revisedLines, i, len;
|
||||
if (selectedText.length > 0) {
|
||||
// Replace adjacent newlines with a single newline, strip any trailing newline
|
||||
cleanSelectedText = selectedText.replace(/\n+/g, '\n').replace(/\n$/, '');
|
||||
lines = cleanSelectedText.split('\n');
|
||||
revisedLines = '';
|
||||
for (i = 0, len = lines.length; i < len; i++) {
|
||||
line = lines[i];
|
||||
revisedLines += choiceStart;
|
||||
// a stand alone x before other text implies that this option is "correct"
|
||||
if (/^\s*x\s+(\S)/i.test(line)) {
|
||||
// Remove the x and any initial whitespace as long as there's more text on the line
|
||||
line = line.replace(/^\s*x\s+(\S)/i, '$1');
|
||||
revisedLines += 'x';
|
||||
} else {
|
||||
revisedLines += ' ';
|
||||
}
|
||||
revisedLines += choiceEnd + ' ' + line + '\n';
|
||||
}
|
||||
return revisedLines;
|
||||
} else {
|
||||
return template;
|
||||
// Function used in many places to extract {{ label:: a hint }}.
|
||||
// Returns a little hash with various parts of the hint:
|
||||
// hint: the hint or empty, nothint: the rest
|
||||
// labelassign: javascript assignment of label attribute, or empty
|
||||
extractHint = function (inputText, detectParens) {
|
||||
var text = inputText,
|
||||
curly = /\s*{{(.*?)}}/.exec(text),
|
||||
hint = "",
|
||||
label = "",
|
||||
parens = false,
|
||||
labelassign = "",
|
||||
labelmatch;
|
||||
if (curly) {
|
||||
text = text.replace(curly[0], "");
|
||||
hint = curly[1].trim();
|
||||
labelmatch = /^(.*?)::/.exec(hint);
|
||||
if (labelmatch) {
|
||||
hint = hint.replace(labelmatch[0], "").trim();
|
||||
label = labelmatch[1].trim();
|
||||
labelassign = ' label="' + label + '"';
|
||||
}
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertStringInput = function(selectedText) {
|
||||
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '= ', '',
|
||||
MarkdownEditingDescriptor.stringInputTemplate
|
||||
);
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertNumberInput = function(selectedText) {
|
||||
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '= ', '',
|
||||
MarkdownEditingDescriptor.numberInputTemplate
|
||||
);
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertSelect = function(selectedText) {
|
||||
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '[[', ']]',
|
||||
MarkdownEditingDescriptor.selectTemplate
|
||||
);
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertHeader = function(selectedText) {
|
||||
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '', '\n====\n',
|
||||
MarkdownEditingDescriptor.headerTemplate
|
||||
);
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertExplanation = function(selectedText) {
|
||||
return MarkdownEditingDescriptor.insertGenericInput(selectedText, '[explanation]\n', '\n[explanation]',
|
||||
MarkdownEditingDescriptor.explanationTemplate
|
||||
);
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.insertGenericInput = function(selectedText, lineStart, lineEnd, template) {
|
||||
if (selectedText.length > 0) {
|
||||
return lineStart + selectedText + lineEnd;
|
||||
} else {
|
||||
return template;
|
||||
}
|
||||
if (detectParens) {
|
||||
if (text.length >= 2 && text[0] === "(" && text[text.length - 1] === ")") {
|
||||
text = text.substring(1, text.length - 1);
|
||||
parens = true;
|
||||
}
|
||||
}
|
||||
return {
|
||||
nothint: text,
|
||||
hint: hint,
|
||||
label: label,
|
||||
parens: parens,
|
||||
labelassign: labelassign,
|
||||
};
|
||||
};
|
||||
|
||||
MarkdownEditingDescriptor.markdownToXml = function(markdown) {
|
||||
var demandHintTags = [],
|
||||
finalDemandHints, finalXml, responseTypesMarkdown, responseTypesXML, toXml;
|
||||
toXml = function(partialMarkdown) {
|
||||
var xml = partialMarkdown,
|
||||
i, splits, makeParagraph, serializer, responseType, $xml, responseTypesSelector,
|
||||
inputtype, beforeInputtype, extractHint, demandhints;
|
||||
var responseTypes = [
|
||||
'optionresponse', 'multiplechoiceresponse', 'stringresponse', 'numericalresponse', 'choiceresponse'
|
||||
];
|
||||
// replace selects
|
||||
// [[ a, b, (c) ]]
|
||||
// [[
|
||||
// a
|
||||
// b
|
||||
// (c)
|
||||
// ]]
|
||||
// <optionresponse>
|
||||
// <optioninput>
|
||||
// <option correct="True">AAA<optionhint label="Good Job">
|
||||
// Yes, multiple choice is the right answer.
|
||||
// </optionhint>
|
||||
// Note: part of the option-response syntax looks like multiple-choice, so it must be processed first.
|
||||
xml = xml.replace(/\[\[((.|\n)+?)\]\]/g, function (match, group1) {
|
||||
var textHint, options, optiontag, correct, lines, optionlines, line, correctstr, hintstr, label;
|
||||
// decide if this is old style or new style
|
||||
if (match.indexOf("\n") === -1) {
|
||||
// OLD style, [[ .... ]] on one line
|
||||
options = group1.split(/\,\s*/g);
|
||||
optiontag = ' <optioninput options="(';
|
||||
for (i = 0; i < options.length; i += 1) {
|
||||
optiontag +=
|
||||
"'" +
|
||||
options[i].replace(/(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g, "$1") +
|
||||
"'" +
|
||||
(i < options.length - 1 ? "," : "");
|
||||
}
|
||||
optiontag += ')" correct="';
|
||||
correct = /(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g.exec(group1);
|
||||
if (correct) {
|
||||
optiontag += correct[1];
|
||||
}
|
||||
optiontag += '">';
|
||||
|
||||
// fix DOS \r\n line endings to look like \n
|
||||
xml = xml.replace(/\r\n/g, '\n');
|
||||
return "\n<optionresponse>\n" + optiontag + "</optioninput>\n</optionresponse>\n\n"; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
|
||||
// replace headers
|
||||
xml = xml.replace(/(^.*?$)(?=\n\=\=+$)/gm, '<h3 class="hd hd-2 problem-header">$1</h3>');
|
||||
xml = xml.replace(/\n^\=\=+$/gm, '');
|
||||
|
||||
// extract question and description(optional)
|
||||
// >>question||description<< converts to
|
||||
// <label>question</label> <description>description</description>
|
||||
xml = xml.replace(/>>([^]+?)<</gm, function(match, questionText) {
|
||||
var result = questionText.split('||'),
|
||||
label = '<label>' + result[0] + '</label>\n'; // xss-lint: disable=javascript-concat-html
|
||||
|
||||
// don't add empty <description> tag
|
||||
if (result.length === 1 || !result[1]) {
|
||||
return label;
|
||||
}
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
return label + '<description>' + result[1] + '</description>\n';
|
||||
});
|
||||
|
||||
// Pull out demand hints, || a hint ||
|
||||
demandhints = '';
|
||||
xml = xml.replace(/(^\s*\|\|.*?\|\|\s*$\n?)+/gm, function(match) { // $\n
|
||||
var inner,
|
||||
options = match.split('\n');
|
||||
for (i = 0; i < options.length; i += 1) {
|
||||
inner = /\s*\|\|(.*?)\|\|/.exec(options[i]);
|
||||
if (inner) {
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
demandhints += ' <hint>' + inner[1].trim() + '</hint>\n';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
// replace \n+whitespace within extended hint {{ .. }}, by a space, so the whole
|
||||
// hint sits on one line.
|
||||
// This is the one instance of {{ ... }} matching that permits \n
|
||||
xml = xml.replace(/{{(.|\n)*?}}/gm, function(match) {
|
||||
return match.replace(/\r?\n( |\t)*/g, ' ');
|
||||
});
|
||||
|
||||
// Function used in many places to extract {{ label:: a hint }}.
|
||||
// Returns a little hash with various parts of the hint:
|
||||
// hint: the hint or empty, nothint: the rest
|
||||
// labelassign: javascript assignment of label attribute, or empty
|
||||
extractHint = function(inputText, detectParens) {
|
||||
var text = inputText,
|
||||
curly = /\s*{{(.*?)}}/.exec(text),
|
||||
hint = '',
|
||||
label = '',
|
||||
parens = false,
|
||||
labelassign = '',
|
||||
labelmatch;
|
||||
if (curly) {
|
||||
text = text.replace(curly[0], '');
|
||||
hint = curly[1].trim();
|
||||
labelmatch = /^(.*?)::/.exec(hint);
|
||||
if (labelmatch) {
|
||||
hint = hint.replace(labelmatch[0], '').trim();
|
||||
label = labelmatch[1].trim();
|
||||
labelassign = ' label="' + label + '"';
|
||||
}
|
||||
}
|
||||
if (detectParens) {
|
||||
if (text.length >= 2 && text[0] === '(' && text[text.length - 1] === ')') {
|
||||
text = text.substring(1, text.length - 1);
|
||||
parens = true;
|
||||
}
|
||||
}
|
||||
return {
|
||||
nothint: text,
|
||||
hint: hint,
|
||||
label: label,
|
||||
parens: parens,
|
||||
labelassign: labelassign
|
||||
};
|
||||
};
|
||||
|
||||
// replace selects
|
||||
// [[ a, b, (c) ]]
|
||||
// [[
|
||||
// a
|
||||
// b
|
||||
// (c)
|
||||
// ]]
|
||||
// <optionresponse>
|
||||
// <optioninput>
|
||||
// <option correct="True">AAA<optionhint label="Good Job">
|
||||
// Yes, multiple choice is the right answer.
|
||||
// </optionhint>
|
||||
// Note: part of the option-response syntax looks like multiple-choice, so it must be processed first.
|
||||
xml = xml.replace(/\[\[((.|\n)+?)\]\]/g, function(match, group1) {
|
||||
var textHint, options, optiontag, correct, lines, optionlines, line, correctstr, hintstr, label;
|
||||
// decide if this is old style or new style
|
||||
if (match.indexOf('\n') === -1) { // OLD style, [[ .... ]] on one line
|
||||
options = group1.split(/\,\s*/g);
|
||||
optiontag = ' <optioninput options="(';
|
||||
for (i = 0; i < options.length; i += 1) {
|
||||
optiontag += "'" + options[i].replace(/(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g, '$1') + "'"
|
||||
+ (i < options.length - 1 ? ',' : '');
|
||||
}
|
||||
optiontag += ')" correct="';
|
||||
correct = /(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g.exec(group1);
|
||||
if (correct) {
|
||||
optiontag += correct[1];
|
||||
}
|
||||
optiontag += '">';
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
return '\n<optionresponse>\n' + optiontag + '</optioninput>\n</optionresponse>\n\n';
|
||||
}
|
||||
|
||||
// new style [[ many-lines ]]
|
||||
lines = group1.split('\n');
|
||||
optionlines = '';
|
||||
for (i = 0; i < lines.length; i++) {
|
||||
line = lines[i].trim();
|
||||
if (line.length > 0) {
|
||||
textHint = extractHint(line, true);
|
||||
if (!textHint.nothint) {
|
||||
throw new Error(gettext('An answer option has been left blank. Please review and edit the component.'));
|
||||
}
|
||||
correctstr = ' correct="' + (textHint.parens ? 'True' : 'False') + '"';
|
||||
hintstr = '';
|
||||
if (textHint.hint) {
|
||||
label = textHint.label;
|
||||
if (label) {
|
||||
label = ' label="' + label + '"';
|
||||
}
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
hintstr = ' <optionhint' + label + '>' + textHint.hint + '</optionhint>';
|
||||
}
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
optionlines += ' <option' + correctstr + '>' + textHint.nothint + hintstr + '</option>\n';
|
||||
}
|
||||
}
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
return '\n<optionresponse>\n <optioninput>\n' + optionlines + ' </optioninput>\n</optionresponse>\n\n';
|
||||
});
|
||||
|
||||
// multiple choice questions
|
||||
//
|
||||
xml = xml.replace(/(^\s*\(.{0,3}\).*?$\n*)+/gm, function(match) {
|
||||
var choices = '',
|
||||
shuffle = false,
|
||||
options = match.split('\n'),
|
||||
value, inparens, correct,
|
||||
fixed, hint, result;
|
||||
for (i = 0; i < options.length; i++) {
|
||||
options[i] = options[i].trim(); // trim off leading/trailing whitespace
|
||||
if (options[i].length > 0) {
|
||||
value = options[i].split(/^\s*\(.{0,3}\)\s*/)[1];
|
||||
if (!value) {
|
||||
throw new Error(gettext('An answer option has been left blank. Please review and edit the component.'));
|
||||
}
|
||||
inparens = /^\s*\((.{0,3})\)\s*/.exec(options[i])[1];
|
||||
correct = /x/i.test(inparens);
|
||||
fixed = '';
|
||||
if (/@/.test(inparens)) {
|
||||
fixed = ' fixed="true"';
|
||||
}
|
||||
if (/!/.test(inparens)) {
|
||||
shuffle = true;
|
||||
}
|
||||
|
||||
hint = extractHint(value);
|
||||
if (hint.hint) {
|
||||
value = hint.nothint;
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
value = value + ' <choicehint' + hint.labelassign + '>' + hint.hint + '</choicehint>';
|
||||
}
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
choices += ' <choice correct="' + correct + '"' + fixed + '>' + value + '</choice>\n';
|
||||
}
|
||||
}
|
||||
result = '<multiplechoiceresponse>\n';
|
||||
if (shuffle) {
|
||||
result += ' <choicegroup type="MultipleChoice" shuffle="true">\n';
|
||||
} else {
|
||||
result += ' <choicegroup type="MultipleChoice">\n';
|
||||
}
|
||||
result += choices;
|
||||
result += ' </choicegroup>\n';
|
||||
result += '</multiplechoiceresponse>\n\n';
|
||||
return result;
|
||||
});
|
||||
|
||||
// group check answers
|
||||
// [.] with {{...}} lines mixed in
|
||||
xml = xml.replace(/(^\s*((\[.?\])|({{.*?}})).*?$\n*)+/gm, function(match) {
|
||||
var groupString = '<choiceresponse>\n',
|
||||
options = match.split('\n'),
|
||||
value, correct, abhint, endHints, hintbody,
|
||||
hint, inner, select, hints;
|
||||
|
||||
groupString += ' <checkboxgroup>\n';
|
||||
endHints = ''; // save these up to emit at the end
|
||||
|
||||
for (i = 0; i < options.length; i += 1) {
|
||||
if (options[i].trim().length > 0) {
|
||||
// detect the {{ ((A*B)) ...}} case first
|
||||
// emits: <compoundhint value="A*B">AB hint</compoundhint>
|
||||
|
||||
abhint = /^\s*{{\s*\(\((.*?)\)\)(.*?)}}/.exec(options[i]);
|
||||
if (abhint) {
|
||||
// lone case of hint text processing outside of extractHint, since syntax here is unique
|
||||
hintbody = abhint[2];
|
||||
hintbody = hintbody.replace('&lf;', '\n').trim();
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
endHints += ' <compoundhint value="' + abhint[1].trim() + '">' + hintbody + '</compoundhint>\n';
|
||||
// eslint-disable-next-line no-continue
|
||||
continue; // bail
|
||||
}
|
||||
|
||||
value = options[i].split(/^\s*\[.?\]\s*/)[1];
|
||||
if (!value) {
|
||||
throw new Error(gettext('An answer option has been left blank. Please review and edit the component.'));
|
||||
}
|
||||
correct = /^\s*\[x\]/i.test(options[i]);
|
||||
hints = '';
|
||||
// {{ selected: You’re right that apple is a fruit. },
|
||||
// {unselected: Remember that apple is also a fruit.}}
|
||||
hint = extractHint(value);
|
||||
if (hint.hint) {
|
||||
inner = '{' + hint.hint + '}'; // parsing is easier if we put outer { } back
|
||||
|
||||
// include \n since we are downstream of extractHint()
|
||||
select = /{\s*(s|selected):((.|\n)*?)}/i.exec(inner);
|
||||
// checkbox choicehints get their own line, since there can be two of them
|
||||
// <choicehint selected="true">You’re right that apple is a fruit.</choicehint>
|
||||
if (select) {
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
hints += '\n <choicehint selected="true">' + select[2].trim() + '</choicehint>';
|
||||
}
|
||||
select = /{\s*(u|unselected):((.|\n)*?)}/i.exec(inner);
|
||||
if (select) {
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
hints += '\n <choicehint selected="false">' + select[2].trim() + '</choicehint>';
|
||||
}
|
||||
|
||||
// Blank out the original text only if the specific "selected" syntax is found
|
||||
// That way, if the user types it wrong, at least they can see it's not processed.
|
||||
if (hints) {
|
||||
value = hint.nothint;
|
||||
}
|
||||
}
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
groupString += ' <choice correct="' + correct + '">' + value + hints + '</choice>\n';
|
||||
}
|
||||
}
|
||||
|
||||
groupString += endHints;
|
||||
groupString += ' </checkboxgroup>\n';
|
||||
groupString += '</choiceresponse>\n\n';
|
||||
|
||||
return groupString;
|
||||
});
|
||||
|
||||
// replace string and numerical, numericalresponse, stringresponse
|
||||
// A fine example of the function-composition programming style.
|
||||
xml = xml.replace(/(^s?\=\s*(.*?$)(\n*(or|not)\=\s*(.*?$))*)+/gm, function(match, p) {
|
||||
// Line split here, trim off leading xxx= in each function
|
||||
var answersList = p.split('\n'),
|
||||
|
||||
isRangeToleranceCase = function(answer) {
|
||||
return _.contains(
|
||||
['[', '('], answer[0]) && _.contains([']', ')'], answer[answer.length - 1]
|
||||
);
|
||||
},
|
||||
|
||||
checkIsNumeric = function(stringValue) {
|
||||
// remove OLX feedback
|
||||
if ((stringValue.indexOf('{{') !== -1) && (stringValue.indexOf('}}') !== -1)) {
|
||||
stringValue = stringValue.replace(/{{[\s\S]*?}}/g, '').trim();
|
||||
}
|
||||
// allow for "e" for scientific notation, otherwise, exclude letters
|
||||
if (stringValue.match(/[a-df-z]/i)) {
|
||||
return false;
|
||||
}
|
||||
return !isNaN(parseFloat(stringValue));
|
||||
},
|
||||
|
||||
getAnswerData = function(answerValue) {
|
||||
var answerData = {},
|
||||
answerParams = /(.*?)\+\-\s*(.*?$)/.exec(answerValue);
|
||||
if (answerParams) {
|
||||
answerData.answer = answerParams[1].replace(/\s+/g, ''); // inputs like 5*2 +- 10
|
||||
answerData.default = answerParams[2];
|
||||
} else {
|
||||
answerData.answer = answerValue.replace(/\s+/g, ''); // inputs like 5*2
|
||||
}
|
||||
return answerData;
|
||||
},
|
||||
|
||||
processNumericalResponse = function(answerValues) {
|
||||
var firstAnswer, answerData, numericalResponseString, additionalAnswerString,
|
||||
textHint, hintLine, additionalTextHint, additionalHintLine, orMatch, hasTolerance;
|
||||
|
||||
// First string case is s?= [e.g. = 100]
|
||||
firstAnswer = answerValues[0].replace(/^\=\s*/, '');
|
||||
|
||||
// If answer is not numerical
|
||||
if (!checkIsNumeric(firstAnswer) && !isRangeToleranceCase(firstAnswer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
textHint = extractHint(firstAnswer);
|
||||
hintLine = '';
|
||||
if (textHint.hint) {
|
||||
firstAnswer = textHint.nothint;
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
hintLine = ' <correcthint' + textHint.labelassign + '>' + textHint.hint + '</correcthint>\n';
|
||||
}
|
||||
|
||||
// Range case
|
||||
if (isRangeToleranceCase(firstAnswer)) {
|
||||
// [5, 7) or (5, 7), or (1.2345 * (2+3), 7*4 ] - range tolerance case
|
||||
// = (5*2)*3 should not be used as range tolerance
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
numericalResponseString = '<numericalresponse answer="' + firstAnswer + '">\n';
|
||||
} else {
|
||||
answerData = getAnswerData(firstAnswer);
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
numericalResponseString = '<numericalresponse answer="' + answerData.answer + '">\n';
|
||||
if (answerData.default) {
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
numericalResponseString += ' <responseparam type="tolerance" default="' + answerData.default + '" />\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Additional answer case or= [e.g. or= 10]
|
||||
// Since answerValues[0] is firstAnswer, so we will not include this in additional answers.
|
||||
additionalAnswerString = '';
|
||||
for (i = 1; i < answerValues.length; i++) {
|
||||
additionalHintLine = '';
|
||||
additionalTextHint = extractHint(answerValues[i]);
|
||||
orMatch = /^or\=\s*(.*)/.exec(additionalTextHint.nothint);
|
||||
if (orMatch) {
|
||||
hasTolerance = /(.*?)\+\-\s*(.*?$)/.exec(orMatch[1]);
|
||||
// Do not add additional_answer if additional answer is not numerical (eg. or= ABC)
|
||||
// or contains range tolerance case (eg. or= (5,7)
|
||||
// or has tolerance (eg. or= 10 +- 0.02)
|
||||
if (isNaN(parseFloat(orMatch[1]))
|
||||
|| isRangeToleranceCase(orMatch[1])
|
||||
|| hasTolerance) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
if (additionalTextHint.hint) {
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
additionalHintLine = '<correcthint' + additionalTextHint.labelassign + '>' + additionalTextHint.hint + '</correcthint>';
|
||||
}
|
||||
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
additionalAnswerString += ' <additional_answer answer="' + orMatch[1] + '">';
|
||||
additionalAnswerString += additionalHintLine;
|
||||
additionalAnswerString += '</additional_answer>\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Add additional answers string to numerical problem string.
|
||||
if (additionalAnswerString) {
|
||||
numericalResponseString += additionalAnswerString;
|
||||
}
|
||||
|
||||
numericalResponseString += ' <formulaequationinput />\n';
|
||||
numericalResponseString += hintLine;
|
||||
numericalResponseString += '</numericalresponse>\n\n';
|
||||
|
||||
return numericalResponseString;
|
||||
},
|
||||
|
||||
processStringResponse = function(values) {
|
||||
var firstAnswer, textHint, typ, string, orMatch, notMatch;
|
||||
// First string case is s?=
|
||||
firstAnswer = values.shift();
|
||||
firstAnswer = firstAnswer.replace(/^s?\=\s*/, '');
|
||||
textHint = extractHint(firstAnswer);
|
||||
firstAnswer = textHint.nothint;
|
||||
typ = ' type="ci"';
|
||||
if (firstAnswer[0] === '|') { // this is regexp case
|
||||
typ = ' type="ci regexp"';
|
||||
firstAnswer = firstAnswer.slice(1).trim();
|
||||
}
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
string = '<stringresponse answer="' + firstAnswer + '"' + typ + ' >\n';
|
||||
if (textHint.hint) {
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
string += ' <correcthint' + textHint.labelassign + '>'
|
||||
+ textHint.hint + '</correcthint>\n'; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
|
||||
// Subsequent cases are not= or or=
|
||||
for (i = 0; i < values.length; i += 1) {
|
||||
textHint = extractHint(values[i]);
|
||||
notMatch = /^not\=\s*(.*)/.exec(textHint.nothint);
|
||||
if (notMatch) {
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
string += ' <stringequalhint answer="' + notMatch[1] + '"' + textHint.labelassign + '>' + textHint.hint + '</stringequalhint>\n';
|
||||
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
orMatch = /^or\=\s*(.*)/.exec(textHint.nothint);
|
||||
if (orMatch) {
|
||||
// additional_answer with answer= attribute
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
string += ' <additional_answer answer="' + orMatch[1] + '">';
|
||||
if (textHint.hint) {
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
string += '<correcthint' + textHint.labelassign + '>' + textHint.hint + '</correcthint>';
|
||||
}
|
||||
string += '</additional_answer>\n';
|
||||
}
|
||||
}
|
||||
|
||||
string += ' <textline size="20"/>\n</stringresponse>\n\n';
|
||||
|
||||
return string;
|
||||
};
|
||||
|
||||
return processNumericalResponse(answersList) || processStringResponse(answersList);
|
||||
});
|
||||
|
||||
// replace explanations
|
||||
xml = xml.replace(/\[explanation\]\n?([^\]]*)\[\/?explanation\]/gmi, function(match, p1) {
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
return '<solution>\n<div class="detailed-solution">\n' + gettext('Explanation') + '\n\n' + p1 + '\n</div>\n</solution>';
|
||||
});
|
||||
|
||||
// replace code blocks
|
||||
xml = xml.replace(/\[code\]\n?([^\]]*)\[\/?code\]/gmi, function(match, p1) {
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
return '<pre><code>' + p1 + '</code></pre>';
|
||||
});
|
||||
|
||||
// split scripts and preformatted sections, and wrap paragraphs
|
||||
splits = xml.split(/(\<\/?(?:script|pre|label|description).*?\>)/g);
|
||||
|
||||
// Wrap a string by <p> tag when line is not already wrapped by another tag
|
||||
// true when line is not already wrapped by another tag false otherwise
|
||||
makeParagraph = true;
|
||||
|
||||
for (i = 0; i < splits.length; i += 1) {
|
||||
if (/\<(script|pre|label|description)/.test(splits[i])) {
|
||||
makeParagraph = false;
|
||||
}
|
||||
|
||||
if (makeParagraph) {
|
||||
splits[i] = splits[i].replace(/(^(?!\s*\<|$).*$)/gm, '<p>$1</p>');
|
||||
}
|
||||
|
||||
if (/\<\/(script|pre|label|description)/.test(splits[i])) {
|
||||
makeParagraph = true;
|
||||
}
|
||||
// new style [[ many-lines ]]
|
||||
lines = group1.split("\n");
|
||||
optionlines = "";
|
||||
for (i = 0; i < lines.length; i++) {
|
||||
line = lines[i].trim();
|
||||
if (line.length > 0) {
|
||||
textHint = extractHint(line, true);
|
||||
if (!textHint.nothint) {
|
||||
throw new Error(gettext("An answer option has been left blank. Please review and edit the component."));
|
||||
}
|
||||
correctstr = ' correct="' + (textHint.parens ? "True" : "False") + '"';
|
||||
hintstr = "";
|
||||
if (textHint.hint) {
|
||||
label = textHint.label;
|
||||
if (label) {
|
||||
label = ' label="' + label + '"';
|
||||
}
|
||||
|
||||
xml = splits.join('');
|
||||
hintstr = " <optionhint" + label + ">" + textHint.hint + "</optionhint>"; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
|
||||
// rid white space
|
||||
xml = xml.replace(/\n\n\n/g, '\n');
|
||||
optionlines += " <option" + correctstr + ">" + textHint.nothint + hintstr + "</option>\n"; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
}
|
||||
|
||||
// if we've come across demand hints, wrap in <demandhint> at the end
|
||||
if (demandhints) {
|
||||
demandHintTags.push(demandhints);
|
||||
return "\n<optionresponse>\n <optioninput>\n" + optionlines + " </optioninput>\n</optionresponse>\n\n"; // xss-lint: disable=javascript-concat-html
|
||||
});
|
||||
|
||||
// multiple choice questions
|
||||
//
|
||||
xml = xml.replace(/(^\s*\(.{0,3}\).*?$\n*)+/gm, function (match) {
|
||||
var choices = "",
|
||||
shuffle = false,
|
||||
options = match.split("\n"),
|
||||
value,
|
||||
inparens,
|
||||
correct,
|
||||
fixed,
|
||||
hint,
|
||||
result;
|
||||
for (i = 0; i < options.length; i++) {
|
||||
options[i] = options[i].trim(); // trim off leading/trailing whitespace
|
||||
if (options[i].length > 0) {
|
||||
value = options[i].split(/^\s*\(.{0,3}\)\s*/)[1];
|
||||
if (!value) {
|
||||
throw new Error(gettext("An answer option has been left blank. Please review and edit the component."));
|
||||
}
|
||||
inparens = /^\s*\((.{0,3})\)\s*/.exec(options[i])[1];
|
||||
correct = /x/i.test(inparens);
|
||||
fixed = "";
|
||||
if (/@/.test(inparens)) {
|
||||
fixed = ' fixed="true"';
|
||||
}
|
||||
if (/!/.test(inparens)) {
|
||||
shuffle = true;
|
||||
}
|
||||
|
||||
hint = extractHint(value);
|
||||
if (hint.hint) {
|
||||
value = hint.nothint;
|
||||
|
||||
value = value + " <choicehint" + hint.labelassign + ">" + hint.hint + "</choicehint>"; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
|
||||
choices += ' <choice correct="' + correct + '"' + fixed + ">" + value + "</choice>\n"; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
}
|
||||
result = "<multiplechoiceresponse>\n";
|
||||
if (shuffle) {
|
||||
result += ' <choicegroup type="MultipleChoice" shuffle="true">\n';
|
||||
} else {
|
||||
result += ' <choicegroup type="MultipleChoice">\n';
|
||||
}
|
||||
result += choices;
|
||||
result += " </choicegroup>\n";
|
||||
result += "</multiplechoiceresponse>\n\n";
|
||||
return result;
|
||||
});
|
||||
|
||||
// group check answers
|
||||
// [.] with {{...}} lines mixed in
|
||||
xml = xml.replace(/(^\s*((\[.?\])|({{.*?}})).*?$\n*)+/gm, function (match) {
|
||||
var groupString = "<choiceresponse>\n",
|
||||
options = match.split("\n"),
|
||||
value,
|
||||
correct,
|
||||
abhint,
|
||||
endHints,
|
||||
hintbody,
|
||||
hint,
|
||||
inner,
|
||||
select,
|
||||
hints;
|
||||
|
||||
groupString += " <checkboxgroup>\n";
|
||||
endHints = ""; // save these up to emit at the end
|
||||
|
||||
for (i = 0; i < options.length; i += 1) {
|
||||
if (options[i].trim().length > 0) {
|
||||
// detect the {{ ((A*B)) ...}} case first
|
||||
// emits: <compoundhint value="A*B">AB hint</compoundhint>
|
||||
|
||||
abhint = /^\s*{{\s*\(\((.*?)\)\)(.*?)}}/.exec(options[i]);
|
||||
if (abhint) {
|
||||
// lone case of hint text processing outside of extractHint, since syntax here is unique
|
||||
hintbody = abhint[2];
|
||||
hintbody = hintbody.replace("&lf;", "\n").trim();
|
||||
|
||||
endHints += ' <compoundhint value="' + abhint[1].trim() + '">' + hintbody + "</compoundhint>\n"; // xss-lint: disable=javascript-concat-html
|
||||
// eslint-disable-next-line no-continue
|
||||
continue; // bail
|
||||
}
|
||||
|
||||
value = options[i].split(/^\s*\[.?\]\s*/)[1];
|
||||
if (!value) {
|
||||
throw new Error(gettext("An answer option has been left blank. Please review and edit the component."));
|
||||
}
|
||||
correct = /^\s*\[x\]/i.test(options[i]);
|
||||
hints = "";
|
||||
// {{ selected: You’re right that apple is a fruit. },
|
||||
// {unselected: Remember that apple is also a fruit.}}
|
||||
hint = extractHint(value);
|
||||
if (hint.hint) {
|
||||
inner = "{" + hint.hint + "}"; // parsing is easier if we put outer { } back
|
||||
|
||||
// include \n since we are downstream of extractHint()
|
||||
select = /{\s*(s|selected):((.|\n)*?)}/i.exec(inner);
|
||||
// checkbox choicehints get their own line, since there can be two of them
|
||||
// <choicehint selected="true">You’re right that apple is a fruit.</choicehint>
|
||||
if (select) {
|
||||
hints += '\n <choicehint selected="true">' + select[2].trim() + "</choicehint>"; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
select = /{\s*(u|unselected):((.|\n)*?)}/i.exec(inner);
|
||||
if (select) {
|
||||
hints += '\n <choicehint selected="false">' + select[2].trim() + "</choicehint>"; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
|
||||
// make selector to search responsetypes in xml
|
||||
responseTypesSelector = responseTypes.join(', ');
|
||||
|
||||
// make temporary xml
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
$xml = $($.parseXML('<prob>' + xml + '</prob>'));
|
||||
responseType = $xml.find(responseTypesSelector);
|
||||
|
||||
// convert if there is only one responsetype
|
||||
if (responseType.length === 1) {
|
||||
inputtype = responseType[0].firstElementChild;
|
||||
// used to decide whether an element should be placed before or after an inputtype
|
||||
beforeInputtype = true;
|
||||
|
||||
_.each($xml.find('prob').children(), function(child) {
|
||||
// we don't want to add the responsetype again into new xml
|
||||
if (responseType[0].nodeName === child.nodeName) {
|
||||
beforeInputtype = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (beforeInputtype) {
|
||||
// xss-lint: disable=javascript-jquery-insert-into-target
|
||||
responseType[0].insertBefore(child, inputtype);
|
||||
} else {
|
||||
responseType[0].appendChild(child);
|
||||
}
|
||||
});
|
||||
serializer = new XMLSerializer();
|
||||
|
||||
xml = serializer.serializeToString(responseType[0]);
|
||||
|
||||
// remove xmlns attribute added by the serializer
|
||||
xml = xml.replace(/\sxmlns=['"].*?['"]/gi, '');
|
||||
|
||||
// XMLSerializer messes the indentation of XML so add newline
|
||||
// at the end of each ending tag to make the xml looks better
|
||||
xml = xml.replace(/(\<\/.*?\>)(\<.*?\>)/gi, '$1\n$2');
|
||||
// Blank out the original text only if the specific "selected" syntax is found
|
||||
// That way, if the user types it wrong, at least they can see it's not processed.
|
||||
if (hints) {
|
||||
value = hint.nothint;
|
||||
}
|
||||
}
|
||||
|
||||
// remove class attribute added on <p> tag for question title
|
||||
xml = xml.replace(/\sclass=\'qtitle\'/gi, '');
|
||||
return xml;
|
||||
groupString += ' <choice correct="' + correct + '">' + value + hints + "</choice>\n"; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
}
|
||||
|
||||
groupString += endHints;
|
||||
groupString += " </checkboxgroup>\n";
|
||||
groupString += "</choiceresponse>\n\n";
|
||||
|
||||
return groupString;
|
||||
});
|
||||
|
||||
// replace string and numerical, numericalresponse, stringresponse
|
||||
// A fine example of the function-composition programming style.
|
||||
xml = xml.replace(/(^s?\=\s*(.*?$)(\n*(or|not)\=\s*(.*?$))*)+/gm, function (match, p) {
|
||||
// Line split here, trim off leading xxx= in each function
|
||||
var answersList = p.split("\n"),
|
||||
isRangeToleranceCase = function (answer) {
|
||||
return _.contains(["[", "("], answer[0]) && _.contains(["]", ")"], answer[answer.length - 1]);
|
||||
},
|
||||
checkIsNumeric = function (stringValue) {
|
||||
// remove OLX feedback
|
||||
if (stringValue.indexOf("{{") !== -1 && stringValue.indexOf("}}") !== -1) {
|
||||
stringValue = stringValue.replace(/{{[\s\S]*?}}/g, "").trim();
|
||||
}
|
||||
// allow for "e" for scientific notation, otherwise, exclude letters
|
||||
if (stringValue.match(/[a-df-z]/i)) {
|
||||
return false;
|
||||
}
|
||||
return !isNaN(parseFloat(stringValue));
|
||||
},
|
||||
getAnswerData = function (answerValue) {
|
||||
var answerData = {},
|
||||
answerParams = /(.*?)\+\-\s*(.*?$)/.exec(answerValue);
|
||||
if (answerParams) {
|
||||
answerData.answer = answerParams[1].replace(/\s+/g, ""); // inputs like 5*2 +- 10
|
||||
answerData.default = answerParams[2];
|
||||
} else {
|
||||
answerData.answer = answerValue.replace(/\s+/g, ""); // inputs like 5*2
|
||||
}
|
||||
return answerData;
|
||||
},
|
||||
processNumericalResponse = function (answerValues) {
|
||||
var firstAnswer,
|
||||
answerData,
|
||||
numericalResponseString,
|
||||
additionalAnswerString,
|
||||
textHint,
|
||||
hintLine,
|
||||
additionalTextHint,
|
||||
additionalHintLine,
|
||||
orMatch,
|
||||
hasTolerance;
|
||||
|
||||
// First string case is s?= [e.g. = 100]
|
||||
firstAnswer = answerValues[0].replace(/^\=\s*/, "");
|
||||
|
||||
// If answer is not numerical
|
||||
if (!checkIsNumeric(firstAnswer) && !isRangeToleranceCase(firstAnswer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
textHint = extractHint(firstAnswer);
|
||||
hintLine = "";
|
||||
if (textHint.hint) {
|
||||
firstAnswer = textHint.nothint;
|
||||
|
||||
hintLine = " <correcthint" + textHint.labelassign + ">" + textHint.hint + "</correcthint>\n"; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
|
||||
// Range case
|
||||
if (isRangeToleranceCase(firstAnswer)) {
|
||||
// [5, 7) or (5, 7), or (1.2345 * (2+3), 7*4 ] - range tolerance case
|
||||
// = (5*2)*3 should not be used as range tolerance
|
||||
|
||||
numericalResponseString = '<numericalresponse answer="' + firstAnswer + '">\n'; // xss-lint: disable=javascript-concat-html
|
||||
} else {
|
||||
answerData = getAnswerData(firstAnswer);
|
||||
|
||||
numericalResponseString = '<numericalresponse answer="' + answerData.answer + '">\n'; // xss-lint: disable=javascript-concat-html
|
||||
if (answerData.default) {
|
||||
numericalResponseString +=
|
||||
' <responseparam type="tolerance" default="' + answerData.default + '" />\n'; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
}
|
||||
|
||||
// Additional answer case or= [e.g. or= 10]
|
||||
// Since answerValues[0] is firstAnswer, so we will not include this in additional answers.
|
||||
additionalAnswerString = "";
|
||||
for (i = 1; i < answerValues.length; i++) {
|
||||
additionalHintLine = "";
|
||||
additionalTextHint = extractHint(answerValues[i]);
|
||||
orMatch = /^or\=\s*(.*)/.exec(additionalTextHint.nothint);
|
||||
if (orMatch) {
|
||||
hasTolerance = /(.*?)\+\-\s*(.*?$)/.exec(orMatch[1]);
|
||||
// Do not add additional_answer if additional answer is not numerical (eg. or= ABC)
|
||||
// or contains range tolerance case (eg. or= (5,7)
|
||||
// or has tolerance (eg. or= 10 +- 0.02)
|
||||
if (isNaN(parseFloat(orMatch[1])) || isRangeToleranceCase(orMatch[1]) || hasTolerance) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
if (additionalTextHint.hint) {
|
||||
additionalHintLine =
|
||||
"<correcthint" + // xss-lint: disable=javascript-concat-html
|
||||
additionalTextHint.labelassign + // xss-lint: disable=javascript-concat-html
|
||||
">" + // xss-lint: disable=javascript-concat-html
|
||||
additionalTextHint.hint + // xss-lint: disable=javascript-concat-html
|
||||
"</correcthint>"; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
|
||||
additionalAnswerString += ' <additional_answer answer="' + orMatch[1] + '">'; // xss-lint: disable=javascript-concat-html
|
||||
additionalAnswerString += additionalHintLine;
|
||||
additionalAnswerString += "</additional_answer>\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Add additional answers string to numerical problem string.
|
||||
if (additionalAnswerString) {
|
||||
numericalResponseString += additionalAnswerString;
|
||||
}
|
||||
|
||||
numericalResponseString += " <formulaequationinput />\n";
|
||||
numericalResponseString += hintLine;
|
||||
numericalResponseString += "</numericalresponse>\n\n";
|
||||
|
||||
return numericalResponseString;
|
||||
},
|
||||
processStringResponse = function (values) {
|
||||
var firstAnswer, textHint, typ, string, orMatch, notMatch;
|
||||
// First string case is s?=
|
||||
firstAnswer = values.shift();
|
||||
firstAnswer = firstAnswer.replace(/^s?\=\s*/, "");
|
||||
textHint = extractHint(firstAnswer);
|
||||
firstAnswer = textHint.nothint;
|
||||
typ = ' type="ci"';
|
||||
if (firstAnswer[0] === "|") {
|
||||
// this is regexp case
|
||||
typ = ' type="ci regexp"';
|
||||
firstAnswer = firstAnswer.slice(1).trim();
|
||||
}
|
||||
|
||||
string = '<stringresponse answer="' + firstAnswer + '"' + typ + " >\n"; // xss-lint: disable=javascript-concat-html
|
||||
if (textHint.hint) {
|
||||
string += " <correcthint" + textHint.labelassign + ">" + textHint.hint + "</correcthint>\n"; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
|
||||
// Subsequent cases are not= or or=
|
||||
for (i = 0; i < values.length; i += 1) {
|
||||
textHint = extractHint(values[i]);
|
||||
notMatch = /^not\=\s*(.*)/.exec(textHint.nothint);
|
||||
if (notMatch) {
|
||||
string +=
|
||||
' <stringequalhint answer="' + // xss-lint: disable=javascript-concat-html
|
||||
notMatch[1] + // xss-lint: disable=javascript-concat-html
|
||||
'"' + // xss-lint: disable=javascript-concat-html
|
||||
textHint.labelassign + // xss-lint: disable=javascript-concat-html
|
||||
">" + // xss-lint: disable=javascript-concat-html
|
||||
textHint.hint + // xss-lint: disable=javascript-concat-html
|
||||
"</stringequalhint>\n"; // xss-lint: disable=javascript-concat-html
|
||||
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
orMatch = /^or\=\s*(.*)/.exec(textHint.nothint);
|
||||
if (orMatch) {
|
||||
// additional_answer with answer= attribute
|
||||
|
||||
string += ' <additional_answer answer="' + orMatch[1] + '">'; // xss-lint: disable=javascript-concat-html
|
||||
if (textHint.hint) {
|
||||
string += "<correcthint" + textHint.labelassign + ">" + textHint.hint + "</correcthint>"; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
string += "</additional_answer>\n";
|
||||
}
|
||||
}
|
||||
|
||||
string += ' <textline size="20"/>\n</stringresponse>\n\n';
|
||||
|
||||
return string;
|
||||
};
|
||||
responseTypesXML = [];
|
||||
responseTypesMarkdown = markdown.split(/\n\s*---\s*\n/g);
|
||||
_.each(responseTypesMarkdown, function(responseTypeMarkdown) {
|
||||
if (responseTypeMarkdown.trim().length > 0) {
|
||||
responseTypesXML.push(toXml(responseTypeMarkdown));
|
||||
}
|
||||
});
|
||||
finalDemandHints = '';
|
||||
if (demandHintTags.length) {
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
finalDemandHints = '\n<demandhint>\n' + demandHintTags.join('') + '</demandhint>';
|
||||
}
|
||||
// make all responsetypes descendants of a single problem element
|
||||
// xss-lint: disable=javascript-concat-html
|
||||
finalXml = '<problem>\n' + responseTypesXML.join('\n\n') + finalDemandHints + '\n</problem>';
|
||||
return finalXml;
|
||||
};
|
||||
|
||||
return MarkdownEditingDescriptor;
|
||||
}(XModule.Descriptor));
|
||||
return processNumericalResponse(answersList) || processStringResponse(answersList);
|
||||
});
|
||||
|
||||
// replace explanations
|
||||
xml = xml.replace(/\[explanation\]\n?([^\]]*)\[\/?explanation\]/gim, function (match, p1) {
|
||||
return (
|
||||
'<solution>\n<div class="detailed-solution">\n' + // xss-lint: disable=javascript-concat-html
|
||||
gettext("Explanation") +
|
||||
"\n\n" + // xss-lint: disable=javascript-concat-html
|
||||
p1 + // xss-lint: disable=javascript-concat-html
|
||||
"\n</div>\n</solution>"
|
||||
);
|
||||
});
|
||||
|
||||
// replace code blocks
|
||||
xml = xml.replace(/\[code\]\n?([^\]]*)\[\/?code\]/gim, function (match, p1) {
|
||||
return "<pre><code>" + p1 + "</code></pre>"; // xss-lint: disable=javascript-concat-html
|
||||
});
|
||||
|
||||
// split scripts and preformatted sections, and wrap paragraphs
|
||||
splits = xml.split(/(<\/?(?:script|pre|label|description)[^>]*>)/gi);
|
||||
|
||||
// Wrap a string by <p> tag when line is not already wrapped by another tag
|
||||
// true when line is not already wrapped by another tag false otherwise
|
||||
makeParagraph = true;
|
||||
|
||||
for (i = 0; i < splits.length; i += 1) {
|
||||
if (/\<(script|pre|label|description)/.test(splits[i])) {
|
||||
makeParagraph = false;
|
||||
}
|
||||
|
||||
if (makeParagraph) {
|
||||
splits[i] = splits[i].replace(/(^(?!\s*\<|$).*$)/gm, "<p>$1</p>");
|
||||
}
|
||||
|
||||
if (/\<\/(script|pre|label|description)/.test(splits[i])) {
|
||||
makeParagraph = true;
|
||||
}
|
||||
}
|
||||
|
||||
xml = splits.join("");
|
||||
|
||||
// rid white space
|
||||
xml = xml.replace(/\n\n\n/g, "\n");
|
||||
|
||||
// if we've come across demand hints, wrap in <demandhint> at the end
|
||||
if (demandhints) {
|
||||
demandHintTags.push(demandhints);
|
||||
}
|
||||
|
||||
// make selector to search responsetypes in xml
|
||||
responseTypesSelector = responseTypes.join(", ");
|
||||
|
||||
// make temporary xml
|
||||
|
||||
$xml = $($.parseXML("<prob>" + xml + "</prob>")); // xss-lint: disable=javascript-concat-html
|
||||
responseType = $xml.find(responseTypesSelector);
|
||||
|
||||
// convert if there is only one responsetype
|
||||
if (responseType.length === 1) {
|
||||
inputtype = responseType[0].firstElementChild;
|
||||
// used to decide whether an element should be placed before or after an inputtype
|
||||
beforeInputtype = true;
|
||||
|
||||
_.each($xml.find("prob").children(), function (child) {
|
||||
// we don't want to add the responsetype again into new xml
|
||||
if (responseType[0].nodeName === child.nodeName) {
|
||||
beforeInputtype = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (beforeInputtype) {
|
||||
// xss-lint: disable=javascript-jquery-insert-into-target
|
||||
responseType[0].insertBefore(child, inputtype);
|
||||
} else {
|
||||
responseType[0].appendChild(child);
|
||||
}
|
||||
});
|
||||
serializer = new XMLSerializer();
|
||||
|
||||
xml = serializer.serializeToString(responseType[0]);
|
||||
|
||||
// remove xmlns attribute added by the serializer
|
||||
xml = xml.replace(/\sxmlns=['"].*?['"]/gi, "");
|
||||
|
||||
// XMLSerializer messes the indentation of XML so add newline
|
||||
// at the end of each ending tag to make the xml looks better
|
||||
xml = xml.replace(/(\<\/.*?\>)(\<.*?\>)/gi, "$1\n$2");
|
||||
}
|
||||
|
||||
// remove class attribute added on <p> tag for question title
|
||||
xml = xml.replace(/\sclass=\'qtitle\'/gi, "");
|
||||
return xml;
|
||||
};
|
||||
responseTypesXML = [];
|
||||
responseTypesMarkdown = markdown.split(/\n\s*---\s*\n/g);
|
||||
_.each(responseTypesMarkdown, function (responseTypeMarkdown) {
|
||||
if (responseTypeMarkdown.trim().length > 0) {
|
||||
responseTypesXML.push(toXml(responseTypeMarkdown));
|
||||
}
|
||||
});
|
||||
finalDemandHints = "";
|
||||
if (demandHintTags.length) {
|
||||
finalDemandHints = "\n<demandhint>\n" + demandHintTags.join("") + "</demandhint>"; // xss-lint: disable=javascript-concat-html
|
||||
}
|
||||
// make all responsetypes descendants of a single problem element
|
||||
|
||||
finalXml = "<problem>\n" + responseTypesXML.join("\n\n") + finalDemandHints + "\n</problem>"; // xss-lint: disable=javascript-concat-html
|
||||
return finalXml;
|
||||
};
|
||||
|
||||
return MarkdownEditingDescriptor;
|
||||
})(XModule.Descriptor);
|
||||
}).call(this);
|
||||
|
||||
@@ -1,32 +1,128 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700");
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input + label.choicegroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ label.choicegroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ label.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ label.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ section.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ section.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicegroup
|
||||
input
|
||||
+ label.choicegroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ label.choicegroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ label.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ label.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ section.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ section.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input + label.choicegroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ label.choicegroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ label.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ label.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ section.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ section.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.incorrect .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.incorrect .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.partially-correct .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.partially-correct .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.correct .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.correct .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline > .incorrect .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput > .incorrect .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
.problem
|
||||
.capa_inputtype.textline
|
||||
> .partially-correct
|
||||
.status
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
.problem
|
||||
.inputtype.formulaequationinput
|
||||
> .partially-correct
|
||||
.status
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline > .correct .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput > .correct .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .partially-correct .status-icon::after,
|
||||
@@ -150,7 +246,7 @@
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .inline+p {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .inline + p {
|
||||
margin-top: var(--baseline, 20px);
|
||||
}
|
||||
|
||||
@@ -159,7 +255,7 @@
|
||||
font-size: var(--small-font-size, 80%);
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem form>label,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem form > label,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .problem-group-label {
|
||||
display: block;
|
||||
margin-bottom: var(--baseline, 20px);
|
||||
@@ -168,12 +264,12 @@
|
||||
-webkit-font-smoothing: initial;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .problem-group-label+.question-description {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .problem-group-label + .question-description {
|
||||
margin-top: calc(-1 * var(--baseline, 20px));
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .wrapper-problem-response+.wrapper-problem-response,
|
||||
.xmodule_display.xmodule_ProblemBlock .wrapper-problem-response+p {
|
||||
.xmodule_display.xmodule_ProblemBlock .wrapper-problem-response + .wrapper-problem-response,
|
||||
.xmodule_display.xmodule_ProblemBlock .wrapper-problem-response + p {
|
||||
margin-top: calc((var(--baseline, 20px) * 1.5));
|
||||
}
|
||||
|
||||
@@ -229,163 +325,557 @@
|
||||
margin-right: calc((var(--baseline, 20px) / 2));
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus + label,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus + label,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover + label,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover + label {
|
||||
border: 2px solid var(--blue, #0075b4);
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label.choicegroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicegroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+label.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+section.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+section.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label.choicegroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicegroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+label.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+section.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+section.choicetextgroup_correct {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input + label.choicegroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input + label.choicegroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input + label.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input + label.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input + section.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input + section.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus + label.choicegroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus + label.choicegroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ label.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus + label.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ section.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus + section.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover + label.choicegroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover + label.choicegroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ label.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover + label.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ section.choicetextgroup_correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover + section.choicetextgroup_correct {
|
||||
border: 2px solid var(--correct, #008100);
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label.choicegroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicegroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+label.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+section.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+section.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label.choicegroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicegroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+label.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+section.choicetextgroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+section.choicetextgroup_correct .status-icon::after {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input + label.choicegroup_correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ label.choicegroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ label.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ label.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ section.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ section.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ label.choicegroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:focus
|
||||
+ label.choicegroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ label.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:focus
|
||||
+ label.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ section.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:focus
|
||||
+ section.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ label.choicegroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:hover
|
||||
+ label.choicegroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ label.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:hover
|
||||
+ label.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ section.choicetextgroup_correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:hover
|
||||
+ section.choicetextgroup_correct
|
||||
.status-icon::after {
|
||||
color: var(--correct, #008100);
|
||||
font-size: 1.2em;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label.choicegroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicegroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+label.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+section.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+section.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label.choicegroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicegroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+label.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+section.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+section.choicetextgroup_partially-correct {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input + label.choicegroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input + label.choicegroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ label.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input + label.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ section.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input + section.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus + label.choicegroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus + label.choicegroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ label.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:focus
|
||||
+ label.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ section.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:focus
|
||||
+ section.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover + label.choicegroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover + label.choicegroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ label.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:hover
|
||||
+ label.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ section.choicetextgroup_partially-correct,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:hover
|
||||
+ section.choicetextgroup_partially-correct {
|
||||
border: 2px solid var(--partially-correct, #008100);
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label.choicegroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicegroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+label.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+section.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+section.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label.choicegroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicegroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+label.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+section.choicetextgroup_partially-correct .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+section.choicetextgroup_partially-correct .status-icon::after {
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicegroup
|
||||
input
|
||||
+ label.choicegroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ label.choicegroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ label.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ label.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ section.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ section.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ label.choicegroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:focus
|
||||
+ label.choicegroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ label.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:focus
|
||||
+ label.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ section.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:focus
|
||||
+ section.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ label.choicegroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:hover
|
||||
+ label.choicegroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ label.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:hover
|
||||
+ label.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ section.choicetextgroup_partially-correct
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:hover
|
||||
+ section.choicetextgroup_partially-correct
|
||||
.status-icon::after {
|
||||
color: var(--partially-correct, #008100);
|
||||
font-size: 1.2em;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label.choicegroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicegroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+label.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+section.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+section.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label.choicegroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicegroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+label.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+section.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+section.choicetextgroup_incorrect {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input + label.choicegroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input + label.choicegroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input + label.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input + label.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ section.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input + section.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus + label.choicegroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus + label.choicegroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ label.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus + label.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ section.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus + section.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover + label.choicegroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover + label.choicegroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ label.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover + label.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ section.choicetextgroup_incorrect,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover + section.choicetextgroup_incorrect {
|
||||
border: 2px solid var(--incorrect, #b20610);
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label.choicegroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicegroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+label.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+section.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+section.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label.choicegroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicegroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+label.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+section.choicetextgroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+section.choicetextgroup_incorrect .status-icon::after {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input + label.choicegroup_incorrect .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ label.choicegroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ label.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ label.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ section.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input
|
||||
+ section.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ label.choicegroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:focus
|
||||
+ label.choicegroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ label.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:focus
|
||||
+ label.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ section.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:focus
|
||||
+ section.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ label.choicegroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:hover
|
||||
+ label.choicegroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ label.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:hover
|
||||
+ label.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ section.choicetextgroup_incorrect
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
input:hover
|
||||
+ section.choicetextgroup_incorrect
|
||||
.status-icon::after {
|
||||
color: var(--incorrect, #b20610);
|
||||
font-size: 1.2em;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label.choicegroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicegroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+label.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+section.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+section.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label.choicegroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicegroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+label.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+section.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+section.choicetextgroup_submitted {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input + label.choicegroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input + label.choicegroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input + label.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input + label.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input
|
||||
+ section.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input + section.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus + label.choicegroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus + label.choicegroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ label.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus + label.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:focus
|
||||
+ section.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus + section.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover + label.choicegroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover + label.choicegroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ label.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover + label.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
.choicegroup
|
||||
input:hover
|
||||
+ section.choicetextgroup_submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover + section.choicetextgroup_submitted {
|
||||
border: 2px solid var(--submitted, #0075b4);
|
||||
}
|
||||
|
||||
@@ -418,7 +908,7 @@
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup legend+.question-description {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup legend + .question-description {
|
||||
margin-top: calc(-1 * var(--baseline, 20px));
|
||||
max-width: 100%;
|
||||
white-space: normal;
|
||||
@@ -453,7 +943,7 @@
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.submitted .status-icon,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.unsubmitted .status-icon,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.unanswered .status-icon {
|
||||
content: '';
|
||||
content: "";
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem ol.enumerate li::before {
|
||||
@@ -463,22 +953,22 @@
|
||||
content: " ";
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .solution-span>span {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .solution-span > span {
|
||||
margin: var(--baseline, 20px) 0;
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .solution-span>span:empty {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .solution-span > span:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .targeted-feedback-span>span {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .targeted-feedback-span > span {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .targeted-feedback-span>span:empty {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .targeted-feedback-span > span:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -567,7 +1057,7 @@
|
||||
}
|
||||
|
||||
@media print {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem div [id^='display'].equation {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem div [id^="display"].equation {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -680,7 +1170,7 @@
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem div .evaluation-response header a {
|
||||
font-size: .85em;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem div .evaluation-scoring .scoring-list {
|
||||
@@ -698,30 +1188,30 @@
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem div .evaluation-scoring .scoring-list li label {
|
||||
font-size: .9em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem div .submit-message-container {
|
||||
margin: var(--baseline, 20px) 0;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem div.inline>span {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem div.inline > span {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem ul {
|
||||
padding-left: 1em;
|
||||
margin-bottom: lh();
|
||||
margin-left: .75em;
|
||||
margin-left: .75rem;
|
||||
margin-left: 0.75em;
|
||||
margin-left: 0.75rem;
|
||||
list-style: disc outside none;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem ol {
|
||||
padding-left: 1em;
|
||||
margin-bottom: lh();
|
||||
margin-left: .75em;
|
||||
margin-left: .75rem;
|
||||
margin-left: 0.75em;
|
||||
margin-left: 0.75rem;
|
||||
list-style: decimal outside none;
|
||||
}
|
||||
|
||||
@@ -734,8 +1224,8 @@
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem dd {
|
||||
margin-left: .5em;
|
||||
margin-left: .5rem;
|
||||
margin-left: 0.5em;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem li {
|
||||
@@ -784,15 +1274,15 @@
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem table caption,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem table th,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem table td {
|
||||
padding: .25em .75em .25em 0;
|
||||
padding: .25rem .75rem .25rem 0;
|
||||
padding: 0.25em 0.75em 0.25em 0;
|
||||
padding: 0.25rem 0.75rem 0.25rem 0;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem table caption {
|
||||
margin-bottom: .75em;
|
||||
margin-bottom: .75rem;
|
||||
padding: .75em 0;
|
||||
padding: .75rem 0;
|
||||
margin-bottom: 0.75em;
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.75em 0;
|
||||
padding: 0.75rem 0;
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
@@ -809,7 +1299,7 @@
|
||||
border-radius: 3px;
|
||||
background-color: var(--gray-l6, #f8f8f8);
|
||||
white-space: nowrap;
|
||||
font-size: .9em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem pre {
|
||||
@@ -818,11 +1308,11 @@
|
||||
border: 1px solid var(--gray-l3, #c8c8c8);
|
||||
border-radius: 3px;
|
||||
background-color: var(--gray-l6, #f8f8f8);
|
||||
font-size: .9em;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem pre>code {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem pre > code {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
@@ -846,73 +1336,98 @@
|
||||
background: none;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.incorrect input,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.incorrect input {
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline > .incorrect input,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput > .incorrect input {
|
||||
border: 2px solid var(--incorrect, #b20610);
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.incorrect .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.incorrect .status .status-icon::after {
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline > .incorrect .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
.problem
|
||||
.inputtype.formulaequationinput
|
||||
> .incorrect
|
||||
.status
|
||||
.status-icon::after {
|
||||
color: var(--incorrect, #b20610);
|
||||
font-size: 1.2em;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.partially-correct input,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.partially-correct input {
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline > .partially-correct input,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput > .partially-correct input {
|
||||
border: 2px solid var(--partially-correct, #008100);
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.partially-correct .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.partially-correct .status .status-icon::after {
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
.problem
|
||||
.capa_inputtype.textline
|
||||
> .partially-correct
|
||||
.status
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
.problem
|
||||
.inputtype.formulaequationinput
|
||||
> .partially-correct
|
||||
.status
|
||||
.status-icon::after {
|
||||
color: var(--partially-correct, #008100);
|
||||
font-size: 1.2em;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.correct input,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.correct input {
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline > .correct input,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput > .correct input {
|
||||
border: 2px solid var(--correct, #008100);
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.correct .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.correct .status .status-icon::after {
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline > .correct .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput > .correct .status .status-icon::after {
|
||||
color: var(--correct, #008100);
|
||||
font-size: 1.2em;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.submitted {
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline > .submitted,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput > .submitted {
|
||||
margin: var(--baseline, 20px) 0 0 0;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.submitted input,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.submitted input {
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline > .submitted input,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput > .submitted input {
|
||||
border: 2px solid var(--submitted, #0075b4);
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.submitted .status,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.submitted .status {
|
||||
content: '';
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline > .submitted .status,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput > .submitted .status {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.unanswered input,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.unsubmitted input,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.unanswered input,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.unsubmitted input {
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline > .unanswered input,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline > .unsubmitted input,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput > .unanswered input,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput > .unsubmitted input {
|
||||
border: 2px solid var(--gray-l4, #e4e4e4);
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.unanswered .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.unsubmitted .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.unanswered .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.unsubmitted .status .status-icon::after {
|
||||
content: '';
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline > .unanswered .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline > .unsubmitted .status .status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
.problem
|
||||
.inputtype.formulaequationinput
|
||||
> .unanswered
|
||||
.status
|
||||
.status-icon::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
.problem
|
||||
.inputtype.formulaequationinput
|
||||
> .unsubmitted
|
||||
.status
|
||||
.status-icon::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>div input {
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput > div input {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
@@ -931,7 +1446,11 @@
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.option-input .indicator-container .status.correct::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.option-input .indicator-container .status.partially-correct::after,
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
.problem
|
||||
.inputtype.option-input
|
||||
.indicator-container
|
||||
.status.partially-correct::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.option-input .indicator-container .status.incorrect::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.option-input .indicator-container .status.submitted::after,
|
||||
.xmodule_display.xmodule_ProblemBlock .problem .inputtype.option-input .indicator-container .status.unanswered::after {
|
||||
@@ -1092,7 +1611,7 @@
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem hr {
|
||||
float: none;
|
||||
clear: both;
|
||||
margin: 0 0 .75rem;
|
||||
margin: 0 0 0.75rem;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border: none;
|
||||
@@ -1121,17 +1640,17 @@
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .detailed-solution>p {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .detailed-solution > p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .detailed-solution>p:first-child {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .detailed-solution > p:first-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .detailed-targeted-feedback>p,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .detailed-targeted-feedback-partially-correct>p,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .detailed-targeted-feedback-correct>p {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .detailed-targeted-feedback > p,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .detailed-targeted-feedback-partially-correct > p,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .detailed-targeted-feedback-correct > p {
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
@@ -1148,7 +1667,8 @@
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .notification {
|
||||
float: left;
|
||||
margin-top: calc(var(--baseline, 20px) / 2);
|
||||
padding: calc((var(--baseline, 20px) / 2.5)) calc((var(--baseline, 20px) / 2)) calc((var(--baseline, 20px) / 5)) calc((var(--baseline, 20px) / 2));
|
||||
padding: calc((var(--baseline, 20px) / 2.5)) calc((var(--baseline, 20px) / 2)) calc((var(--baseline, 20px) / 5))
|
||||
calc((var(--baseline, 20px) / 2));
|
||||
line-height: var(--base-line-height, 1.5em);
|
||||
}
|
||||
|
||||
@@ -1231,7 +1751,7 @@
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .notification li[class*="hint-index-"] ul,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .notification li[class*="hint-index-"] ol {
|
||||
padding: 0 0 0 1em;
|
||||
margin-left: .75rem;
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .notification li[class*="hint-index-"] ol {
|
||||
@@ -1289,7 +1809,7 @@
|
||||
margin-bottom: calc(var(--baseline, 20px) / 2);
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .capa_reset>h2 {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .capa_reset > h2 {
|
||||
color: #a00;
|
||||
}
|
||||
|
||||
@@ -1328,7 +1848,7 @@
|
||||
box-shadow: inset 0 0 0 1px var(--white, #fff);
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .hints div>section {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .hints div > section {
|
||||
padding: 9px;
|
||||
}
|
||||
|
||||
@@ -1346,7 +1866,7 @@
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .test>section {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .test > section {
|
||||
position: relative;
|
||||
margin-bottom: calc((var(--baseline, 20px) / 2));
|
||||
padding: 9px 9px var(--baseline, 20px);
|
||||
@@ -1356,15 +1876,15 @@
|
||||
box-shadow: inset 0 0 0 1px #eee;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .test>section p:last-of-type {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .test > section p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .test>section .shortform {
|
||||
margin-bottom: .6em;
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .test > section .shortform {
|
||||
margin-bottom: 0.6em;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .test>section a.full {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .test > section a.full {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
@@ -1378,7 +1898,7 @@
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .test>section a.full.full-top {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .test > section a.full.full-top {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 0;
|
||||
@@ -1386,7 +1906,7 @@
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .test>section a.full.full-bottom {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .test > section a.full.full-bottom {
|
||||
position: absolute;
|
||||
top: auto;
|
||||
right: 0;
|
||||
@@ -1414,7 +1934,8 @@
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-errors {
|
||||
margin: calc((var(--baseline, 20px) / 4));
|
||||
padding: calc((var(--baseline, 20px) / 2)) calc((var(--baseline, 20px) / 2)) calc((var(--baseline, 20px) / 2)) calc((var(--baseline, 20px) * 2));
|
||||
padding: calc((var(--baseline, 20px) / 2)) calc((var(--baseline, 20px) / 2)) calc((var(--baseline, 20px) / 2))
|
||||
calc((var(--baseline, 20px) * 2));
|
||||
background: var(--icon-incorrect) center left no-repeat;
|
||||
}
|
||||
|
||||
@@ -1450,7 +1971,13 @@
|
||||
background: var(--icon-correct) left 20px no-repeat;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-correct .result-actual-output {
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.external-grader-message
|
||||
section
|
||||
.longform
|
||||
.result-correct
|
||||
.result-actual-output {
|
||||
color: #090;
|
||||
}
|
||||
|
||||
@@ -1458,7 +1985,13 @@
|
||||
background: var(--icon-partially-correct) left 20px no-repeat;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-partially-correct .result-actual-output {
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.external-grader-message
|
||||
section
|
||||
.longform
|
||||
.result-partially-correct
|
||||
.result-actual-output {
|
||||
color: #090;
|
||||
}
|
||||
|
||||
@@ -1466,7 +1999,13 @@
|
||||
background: var(--icon-incorrect) left 20px no-repeat;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-incorrect .result-actual-output {
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.external-grader-message
|
||||
section
|
||||
.longform
|
||||
.result-incorrect
|
||||
.result-actual-output {
|
||||
color: #b00;
|
||||
}
|
||||
|
||||
@@ -1512,7 +2051,7 @@
|
||||
width: 150px;
|
||||
height: 100%;
|
||||
background-color: var(--gray-l3, #c8c8c8);
|
||||
font-size: .9em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .rubric .grade {
|
||||
@@ -1527,12 +2066,12 @@
|
||||
color: white;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .rubric input[type=radio]:checked+label {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .rubric input[type="radio"]:checked + label {
|
||||
background: #666;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .rubric input[class='score-selection'] {
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .rubric input[class="score-selection"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1544,12 +2083,12 @@
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .annotation-header {
|
||||
padding: .5em 1em;
|
||||
padding: 0.5em 1em;
|
||||
border-bottom: 1px solid var(--gray-l3, #c8c8c8);
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .annotation-body {
|
||||
padding: .5em 1em;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input a.annotation-return {
|
||||
@@ -1564,12 +2103,12 @@
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .block,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input ul.tags {
|
||||
margin: .5em 0;
|
||||
margin: 0.5em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .block-highlight {
|
||||
padding: .5em;
|
||||
padding: 0.5em;
|
||||
border: 1px solid rgba(214, 214, 0, 0.3);
|
||||
background-color: rgba(255, 255, 10, 0.3);
|
||||
color: #333;
|
||||
@@ -1609,7 +2148,7 @@
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input ul.tags li .tag-status,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input ul.tags li .tag {
|
||||
padding: .25em .5em;
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input textarea.comment {
|
||||
@@ -1656,8 +2195,16 @@
|
||||
border-color: var(--correct, #008100);
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup label.choicetextgroup_partially-correct input[type="text"],
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup section.choicetextgroup_partially-correct input[type="text"] {
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
label.choicetextgroup_partially-correct
|
||||
input[type="text"],
|
||||
.xmodule_display.xmodule_ProblemBlock
|
||||
div.problem
|
||||
.choicetextgroup
|
||||
section.choicetextgroup_partially-correct
|
||||
input[type="text"] {
|
||||
border-color: var(--partially-correct, #008100);
|
||||
}
|
||||
|
||||
@@ -1681,7 +2228,7 @@
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .status.unsubmitted .status-icon,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .status.unanswered .status-icon {
|
||||
content: '';
|
||||
content: "";
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .status.unsubmitted .status-message,
|
||||
@@ -1708,7 +2255,7 @@
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .submitted {
|
||||
content: '';
|
||||
content: "";
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .tag-status {
|
||||
@@ -1721,7 +2268,7 @@
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .tag-status.unsubmitted .status-icon,
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .tag-status.unanswered .status-icon {
|
||||
content: '';
|
||||
content: "";
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .tag-status.unsubmitted .status-message,
|
||||
@@ -1748,7 +2295,7 @@
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .submitted {
|
||||
content: '';
|
||||
content: "";
|
||||
}
|
||||
|
||||
.xmodule_display.xmodule_ProblemBlock .problems-wrapper .loading-spinner {
|
||||
|
||||
@@ -207,7 +207,7 @@
|
||||
background: none;
|
||||
}
|
||||
|
||||
.xmodule_edit.xmodule_ProblemBlock .problem-editor .markdown-box+.CodeMirror {
|
||||
.xmodule_edit.xmodule_ProblemBlock .problem-editor .markdown-box + .CodeMirror {
|
||||
padding: 10px;
|
||||
width: 69%;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Blank Common Problem
|
||||
markdown: ""
|
||||
display_name: Blank Common Problem
|
||||
markdown: ""
|
||||
data: "<problem></problem>"
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Checkboxes
|
||||
markdown: |
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes problems. Edit this component to replace this template with your own assessment.
|
||||
display_name: Checkboxes
|
||||
markdown: |
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes problems. Edit this component to replace this template with your own assessment.
|
||||
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
|
||||
[x] a correct answer
|
||||
[ ] an incorrect answer
|
||||
[ ] an incorrect answer
|
||||
[x] a correct answer
|
||||
|
||||
data: |
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">a correct answer</choice>
|
||||
<choice correct="false">an incorrect answer</choice>
|
||||
<choice correct="false">an incorrect answer</choice>
|
||||
<choice correct="true">a correct answer</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
</problem>
|
||||
[x] a correct answer
|
||||
[ ] an incorrect answer
|
||||
[ ] an incorrect answer
|
||||
[x] a correct answer
|
||||
|
||||
data: |
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">a correct answer</choice>
|
||||
<choice correct="false">an incorrect answer</choice>
|
||||
<choice correct="false">an incorrect answer</choice>
|
||||
<choice correct="true">a correct answer</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
</problem>
|
||||
|
||||
@@ -1,52 +1,51 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Checkboxes with Hints and Feedback
|
||||
markdown: |
|
||||
display_name: Checkboxes with Hints and Feedback
|
||||
markdown: |
|
||||
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes with hints and feedback problems. Edit this component to replace this template with your own assessment.
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes with hints and feedback problems. Edit this component to replace this template with your own assessment.
|
||||
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this.<<
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this.<<
|
||||
|
||||
[x] a correct answer {{ selected: You can specify optional feedback that appears after the learner selects and submits this answer. }, { unselected: You can specify optional feedback that appears after the learner clears and submits this answer.}}
|
||||
[ ] an incorrect answer
|
||||
[ ] an incorrect answer {{ selected: You can specify optional feedback for none, all, or a subset of the answers. }, { unselected: You can specify optional feedback for selected answers, cleared answers, or both.}}
|
||||
[x] a correct answer
|
||||
[x] a correct answer {{ selected: You can specify optional feedback that appears after the learner selects and submits this answer. }, { unselected: You can specify optional feedback that appears after the learner clears and submits this answer.}}
|
||||
[ ] an incorrect answer
|
||||
[ ] an incorrect answer {{ selected: You can specify optional feedback for none, all, or a subset of the answers. }, { unselected: You can specify optional feedback for selected answers, cleared answers, or both.}}
|
||||
[x] a correct answer
|
||||
|
||||
|
||||
{{ ((A B D)) You can specify optional feedback for a combination of answers which appears after the specified set of answers is submitted. }}
|
||||
{{ ((A B C D)) You can specify optional feedback for one, several, or all answer combinations. }}
|
||||
|
||||
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
|
||||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
|
||||
{{ ((A B D)) You can specify optional feedback for a combination of answers which appears after the specified set of answers is submitted. }}
|
||||
{{ ((A B C D)) You can specify optional feedback for one, several, or all answer combinations. }}
|
||||
|
||||
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
|
||||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
|
||||
|
||||
hinted: true
|
||||
data: |
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">a correct answer
|
||||
<choicehint selected="true">You can specify optional feedback that appears after the learner selects and submits this answer.</choicehint>
|
||||
<choicehint selected="false">You can specify optional feedback that appears after the learner clears and submits this answer.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">an incorrect answer
|
||||
</choice>
|
||||
<choice correct="false">an incorrect answer
|
||||
<choicehint selected="true">You can specify optional feedback for none, all, or a subset of the answers.</choicehint>
|
||||
<choicehint selected="false">You can specify optional feedback for selected answers, cleared answers, or both.</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">a correct answer
|
||||
</choice>
|
||||
<compoundhint value="A B D">You can specify optional feedback for a combination of answers which appears after the specified set of answers is submitted.</compoundhint>
|
||||
<compoundhint value="A B C D">You can specify optional feedback for one, several, or all answer combinations.</compoundhint>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
data: |
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">a correct answer
|
||||
<choicehint selected="true">You can specify optional feedback that appears after the learner selects and submits this answer.</choicehint>
|
||||
<choicehint selected="false">You can specify optional feedback that appears after the learner clears and submits this answer.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">an incorrect answer
|
||||
</choice>
|
||||
<choice correct="false">an incorrect answer
|
||||
<choicehint selected="true">You can specify optional feedback for none, all, or a subset of the answers.</choicehint>
|
||||
<choicehint selected="false">You can specify optional feedback for selected answers, cleared answers, or both.</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">a correct answer
|
||||
</choice>
|
||||
<compoundhint value="A B D">You can specify optional feedback for a combination of answers which appears after the specified set of answers is submitted.</compoundhint>
|
||||
<compoundhint value="A B C D">You can specify optional feedback for one, several, or all answer combinations.</compoundhint>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
|
||||
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
<demandhint>
|
||||
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
|
||||
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
|
||||
@@ -1,89 +1,89 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Circuit Schematic Builder
|
||||
markdown: !!null
|
||||
display_name: Circuit Schematic Builder
|
||||
markdown: !!null
|
||||
data: |
|
||||
<problem>
|
||||
<p>
|
||||
Circuit schematic problems allow students to create virtual circuits by
|
||||
arranging elements such as voltage sources, capacitors, resistors, and
|
||||
MOSFETs on an interactive grid. The system evaluates a DC, AC, or
|
||||
transient analysis of the circuit.
|
||||
</p>
|
||||
<p>
|
||||
For more information, see
|
||||
<a href="https://docs.openedx.org/en/latest/educators/how-tos/course_development/exercise_tools/add_circuit_schematic_builder.html" target="_blank">
|
||||
Circuit Schematic Builder Problem</a> in <i>Building and Running an Open edX Course</i>.
|
||||
</p>
|
||||
<p>
|
||||
When you add the problem, be sure to select <strong>Settings</strong>
|
||||
to specify a <strong>Display Name</strong> and other values that apply.
|
||||
</p>
|
||||
<p>You can use the following example problems as models.</p>
|
||||
<problem>
|
||||
<p>
|
||||
Circuit schematic problems allow students to create virtual circuits by
|
||||
arranging elements such as voltage sources, capacitors, resistors, and
|
||||
MOSFETs on an interactive grid. The system evaluates a DC, AC, or
|
||||
transient analysis of the circuit.
|
||||
</p>
|
||||
<p>
|
||||
For more information, see
|
||||
<a href="https://docs.openedx.org/en/latest/educators/how-tos/course_development/exercise_tools/add_circuit_schematic_builder.html" target="_blank">
|
||||
Circuit Schematic Builder Problem</a> in <i>Building and Running an Open edX Course</i>.
|
||||
</p>
|
||||
<p>
|
||||
When you add the problem, be sure to select <strong>Settings</strong>
|
||||
to specify a <strong>Display Name</strong> and other values that apply.
|
||||
</p>
|
||||
<p>You can use the following example problems as models.</p>
|
||||
|
||||
<schematicresponse>
|
||||
<p>Make a voltage divider that splits the provided voltage evenly.</p>
|
||||
<center>
|
||||
<schematic height="500" width="600" parts="g,r" analyses="dc"
|
||||
initial_value="[["v",[168,144,0],{"value":"dc(1)","_json_":0},["1","0"]],["r",[296,120,0],{"r":"1","_json_":1},["1","output"]],["L",[296,168,3],{"label":"output","_json_":2},["output"]],["w",[296,216,168,216]],["w",[168,216,168,192]],["w",[168,144,168,120]],["w",[168,120,296,120]],["g",[168,216,0],{"_json_":7},["0"]],["view",-67.49999999999994,-78.49999999999994,1.6000000000000003,"50","10","1G",null,"100","1","1000"]]"/>
|
||||
</center>
|
||||
<answer type="loncapa/python">
|
||||
dc_value = "dc analysis not found"
|
||||
for response in submission[0]:
|
||||
if response[0] == 'dc':
|
||||
for node in response[1:]:
|
||||
dc_value = node['output']
|
||||
<schematicresponse>
|
||||
<p>Make a voltage divider that splits the provided voltage evenly.</p>
|
||||
<center>
|
||||
<schematic height="500" width="600" parts="g,r" analyses="dc"
|
||||
initial_value="[["v",[168,144,0],{"value":"dc(1)","_json_":0},["1","0"]],["r",[296,120,0],{"r":"1","_json_":1},["1","output"]],["L",[296,168,3],{"label":"output","_json_":2},["output"]],["w",[296,216,168,216]],["w",[168,216,168,192]],["w",[168,144,168,120]],["w",[168,120,296,120]],["g",[168,216,0],{"_json_":7},["0"]],["view",-67.49999999999994,-78.49999999999994,1.6000000000000003,"50","10","1G",null,"100","1","1000"]]"/>
|
||||
</center>
|
||||
<answer type="loncapa/python">
|
||||
dc_value = "dc analysis not found"
|
||||
for response in submission[0]:
|
||||
if response[0] == 'dc':
|
||||
for node in response[1:]:
|
||||
dc_value = node['output']
|
||||
|
||||
if dc_value == .5:
|
||||
correct = ['correct']
|
||||
else:
|
||||
correct = ['incorrect']
|
||||
</answer>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>
|
||||
You can form a voltage divider that evenly divides the input
|
||||
voltage with two identically valued resistors, with the sampled
|
||||
voltage taken in between the two.
|
||||
</p>
|
||||
<p><img src="/static/images/voltage_divider.png" alt=""/></p>
|
||||
</div>
|
||||
</solution>
|
||||
</schematicresponse>
|
||||
if dc_value == .5:
|
||||
correct = ['correct']
|
||||
else:
|
||||
correct = ['incorrect']
|
||||
</answer>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>
|
||||
You can form a voltage divider that evenly divides the input
|
||||
voltage with two identically valued resistors, with the sampled
|
||||
voltage taken in between the two.
|
||||
</p>
|
||||
<p><img src="/static/images/voltage_divider.png" alt=""/></p>
|
||||
</div>
|
||||
</solution>
|
||||
</schematicresponse>
|
||||
|
||||
<schematicresponse>
|
||||
<p>Make a high-pass filter.</p>
|
||||
<center>
|
||||
<schematic height="500" width="600" parts="g,r,s,c" analyses="ac"
|
||||
submit_analyses="{"ac":[["NodeA",1,9]]}"
|
||||
initial_value="[["v",[160,152,0],{"name":"v1","value":"sin(0,1,1,0,0)","_json_":0},["1","0"]],["w",[160,200,240,200]],["g",[160,200,0],{"_json_":2},["0"]],["L",[240,152,3],{"label":"NodeA","_json_":3},["NodeA"]],["s",[240,152,0],{"color":"cyan","offset":"0","_json_":4},["NodeA"]],["view",64.55878906250004,54.114697265625054,2.5000000000000004,"50","10","1G",null,"100","1","1000"]]"/>
|
||||
</center>
|
||||
<answer type="loncapa/python">
|
||||
ac_values = None
|
||||
for response in submission[0]:
|
||||
if response[0] == 'ac':
|
||||
for node in response[1:]:
|
||||
ac_values = node['NodeA']
|
||||
print("the ac analysis value:", ac_values)
|
||||
if ac_values == None:
|
||||
correct = ['incorrect']
|
||||
elif ac_values[0][1] < ac_values[1][1]:
|
||||
correct = ['correct']
|
||||
else:
|
||||
correct = ['incorrect']
|
||||
</answer>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>
|
||||
You can form a simple high-pass filter without any further
|
||||
constraints by simply putting a resistor in series with a
|
||||
capacitor. The actual values of the components do not really
|
||||
matter in this problem.
|
||||
</p>
|
||||
<p><img src="/static/images/high_pass_filter.png" alt=""/></p>
|
||||
</div>
|
||||
</solution>
|
||||
</schematicresponse>
|
||||
</problem>
|
||||
<schematicresponse>
|
||||
<p>Make a high-pass filter.</p>
|
||||
<center>
|
||||
<schematic height="500" width="600" parts="g,r,s,c" analyses="ac"
|
||||
submit_analyses="{"ac":[["NodeA",1,9]]}"
|
||||
initial_value="[["v",[160,152,0],{"name":"v1","value":"sin(0,1,1,0,0)","_json_":0},["1","0"]],["w",[160,200,240,200]],["g",[160,200,0],{"_json_":2},["0"]],["L",[240,152,3],{"label":"NodeA","_json_":3},["NodeA"]],["s",[240,152,0],{"color":"cyan","offset":"0","_json_":4},["NodeA"]],["view",64.55878906250004,54.114697265625054,2.5000000000000004,"50","10","1G",null,"100","1","1000"]]"/>
|
||||
</center>
|
||||
<answer type="loncapa/python">
|
||||
ac_values = None
|
||||
for response in submission[0]:
|
||||
if response[0] == 'ac':
|
||||
for node in response[1:]:
|
||||
ac_values = node['NodeA']
|
||||
print("the ac analysis value:", ac_values)
|
||||
if ac_values == None:
|
||||
correct = ['incorrect']
|
||||
elif ac_values[0][1] < ac_values[1][1]:
|
||||
correct = ['correct']
|
||||
else:
|
||||
correct = ['incorrect']
|
||||
</answer>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>
|
||||
You can form a simple high-pass filter without any further
|
||||
constraints by simply putting a resistor in series with a
|
||||
capacitor. The actual values of the components do not really
|
||||
matter in this problem.
|
||||
</p>
|
||||
<p><img src="/static/images/high_pass_filter.png" alt=""/></p>
|
||||
</div>
|
||||
</solution>
|
||||
</schematicresponse>
|
||||
</problem>
|
||||
|
||||
@@ -1,81 +1,81 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Custom Python-Evaluated Input
|
||||
markdown: !!null
|
||||
display_name: Custom Python-Evaluated Input
|
||||
markdown: !!null
|
||||
data: |
|
||||
<problem>
|
||||
<p>
|
||||
In custom Python-evaluated input (also called "write-your-own-grader"
|
||||
problems), the grader uses a Python script that you create and embed in
|
||||
the problem to evaluate a learner's response or provide hints. These
|
||||
problems can be any type. Numerical input and text input problems are
|
||||
the most common write-your-own-grader problems.
|
||||
</p>
|
||||
<p>
|
||||
You can use script tag format or answer tag format to create these problems.
|
||||
</p>
|
||||
<p>
|
||||
You can create custom Python-evaluated input problems that provide
|
||||
partial credit or that randomize variables in the Python code. You can
|
||||
also add images to the solution by using an HTML "img" tag. Note that
|
||||
the "img" tag must be between the "div" tags that are inside the
|
||||
"solution" tags, and that learners do not see these images until they
|
||||
click the "Show Answer" button.
|
||||
</p>
|
||||
<p>
|
||||
For more information, see <a href="https://docs.openedx.org/en/latest/educators/references/course_development/exercise_tools/guide_custom_python_problem.html" target="_blank">
|
||||
Write-Your-Own-Grader Problem</a> in <i>Building and Running an Open edX Course</i>.
|
||||
</p>
|
||||
<p>
|
||||
When you add the problem, be sure to select <strong>Settings</strong>
|
||||
to specify a <strong>Display Name</strong> and other values that apply.
|
||||
</p>
|
||||
<p>You can use the following example problem as a model.</p>
|
||||
<hr/>
|
||||
<problem>
|
||||
<p>
|
||||
In custom Python-evaluated input (also called "write-your-own-grader"
|
||||
problems), the grader uses a Python script that you create and embed in
|
||||
the problem to evaluate a learner's response or provide hints. These
|
||||
problems can be any type. Numerical input and text input problems are
|
||||
the most common write-your-own-grader problems.
|
||||
</p>
|
||||
<p>
|
||||
You can use script tag format or answer tag format to create these problems.
|
||||
</p>
|
||||
<p>
|
||||
You can create custom Python-evaluated input problems that provide
|
||||
partial credit or that randomize variables in the Python code. You can
|
||||
also add images to the solution by using an HTML "img" tag. Note that
|
||||
the "img" tag must be between the "div" tags that are inside the
|
||||
"solution" tags, and that learners do not see these images until they
|
||||
click the "Show Answer" button.
|
||||
</p>
|
||||
<p>
|
||||
For more information, see <a href="https://docs.openedx.org/en/latest/educators/references/course_development/exercise_tools/guide_custom_python_problem.html" target="_blank">
|
||||
Write-Your-Own-Grader Problem</a> in <i>Building and Running an Open edX Course</i>.
|
||||
</p>
|
||||
<p>
|
||||
When you add the problem, be sure to select <strong>Settings</strong>
|
||||
to specify a <strong>Display Name</strong> and other values that apply.
|
||||
</p>
|
||||
<p>You can use the following example problem as a model.</p>
|
||||
<hr/>
|
||||
|
||||
<customresponse cfn="test_add_to_ten">
|
||||
<script type="loncapa/python">
|
||||
<customresponse cfn="test_add_to_ten">
|
||||
<script type="loncapa/python">
|
||||
|
||||
def test_add_to_ten(expect, ans):
|
||||
return test_add(10, ans)
|
||||
def test_add_to_ten(expect, ans):
|
||||
return test_add(10, ans)
|
||||
|
||||
</script>
|
||||
<label>Enter two integers that sum to 10.</label>
|
||||
<textline size="40" correct_answer="3" label="Enter first number" /><br/>
|
||||
<textline size="40" correct_answer="7" label="Enter second number" />
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Any set of integers on the line \(y = 10 - x\) satisfy these constraints.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</customresponse>
|
||||
</script>
|
||||
<label>Enter two integers that sum to 10.</label>
|
||||
<textline size="40" correct_answer="3" label="Enter first number" /><br/>
|
||||
<textline size="40" correct_answer="7" label="Enter second number" />
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Any set of integers on the line \(y = 10 - x\) satisfy these constraints.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</customresponse>
|
||||
|
||||
<customresponse cfn="test_add" expect="20">
|
||||
<script type="loncapa/python">
|
||||
<customresponse cfn="test_add" expect="20">
|
||||
<script type="loncapa/python">
|
||||
|
||||
def test_add(expect, ans):
|
||||
try:
|
||||
a1=int(ans[0])
|
||||
a2=int(ans[1])
|
||||
return (a1+a2) == int(expect)
|
||||
except ValueError:
|
||||
return False
|
||||
def test_add(expect, ans):
|
||||
try:
|
||||
a1=int(ans[0])
|
||||
a2=int(ans[1])
|
||||
return (a1+a2) == int(expect)
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
</script>
|
||||
<label>Enter two integers that sum to 20.</label>
|
||||
<textline size="40" correct_answer="11" label="Enter first number" /><br/>
|
||||
<textline size="40" correct_answer="9" label="Enter second number" />
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Any set of integers on the line \(y = 20 - x\) satisfy these constraints.</p>
|
||||
<p>To add an image to the solution, use an HTML "img" tag. Make sure to include alt text.</p>
|
||||
<img src="/static/images/placeholder-image.png" width="400"
|
||||
alt="Description of image, with a primary goal of explaining its
|
||||
relevance to the problem or concept being illustrated for someone
|
||||
who is unable to see the image."/>
|
||||
</div>
|
||||
</solution>
|
||||
</customresponse>
|
||||
</problem>
|
||||
</script>
|
||||
<label>Enter two integers that sum to 20.</label>
|
||||
<textline size="40" correct_answer="11" label="Enter first number" /><br/>
|
||||
<textline size="40" correct_answer="9" label="Enter second number" />
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Any set of integers on the line \(y = 20 - x\) satisfy these constraints.</p>
|
||||
<p>To add an image to the solution, use an HTML "img" tag. Make sure to include alt text.</p>
|
||||
<img src="/static/images/placeholder-image.png" width="400"
|
||||
alt="Description of image, with a primary goal of explaining its
|
||||
relevance to the problem or concept being illustrated for someone
|
||||
who is unable to see the image."/>
|
||||
</div>
|
||||
</solution>
|
||||
</customresponse>
|
||||
</problem>
|
||||
|
||||
@@ -1,85 +1,85 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Drag and Drop (Deprecated Version)
|
||||
markdown: !!null
|
||||
showanswer: never
|
||||
display_name: Drag and Drop (Deprecated Version)
|
||||
markdown: !!null
|
||||
showanswer: never
|
||||
data: |
|
||||
<problem>
|
||||
<p>In drag and drop problems, students respond to a question by dragging text or objects to a specific location on an image.</p>
|
||||
<p>
|
||||
For more information, see
|
||||
<a href="https://docs.openedx.org/en/latest/educators/references/course_development/exercise_tools/guide_drag_and_drop.html" target="_blank">
|
||||
Drag and Drop Problem (Deprecated)</a> in <i>Building and Running an Open edX Course</i>.
|
||||
</p>
|
||||
<p>
|
||||
When you add the problem, be sure to select <strong>Settings</strong>
|
||||
to specify a <strong>Display Name</strong> and other values that apply.
|
||||
</p>
|
||||
<p>You can use the following example problems as models.</p>
|
||||
<hr/>
|
||||
<customresponse>
|
||||
<h3>Simple Drag and Drop</h3>
|
||||
<p>Drag each word in the scrollbar to the bucket that matches the number of letters in the word.</p>
|
||||
<drag_and_drop_input img="https://studio.edx.org/c4x/edX/DemoX/asset/L9_buckets.png">
|
||||
<draggable id="1" label="a"/>
|
||||
<draggable id="2" label="bog"/>
|
||||
<draggable id="3" label="droll"/>
|
||||
<draggable id="4" label="oboe"/>
|
||||
<draggable id="5" label="swain"/>
|
||||
<draggable id="6" label="in"/>
|
||||
<draggable id="7" label="onyx"/>
|
||||
<draggable id="8" label="of"/>
|
||||
<draggable id="9" label="tap"/>
|
||||
<draggable id="10" label="strop"/>
|
||||
<draggable id="11" label="few"/>
|
||||
</drag_and_drop_input>
|
||||
<answer type="loncapa/python">
|
||||
correct_answer = {
|
||||
'1': [[70, 150], 121],
|
||||
'6': [[190, 150], 121],
|
||||
'8': [[190, 150], 121],
|
||||
'2': [[310, 150], 121],
|
||||
'9': [[310, 150], 121],
|
||||
'11': [[310, 150], 121],
|
||||
'4': [[420, 150], 121],
|
||||
'7': [[420, 150], 121],
|
||||
'3': [[550, 150], 121],
|
||||
'5': [[550, 150], 121],
|
||||
'10': [[550, 150], 121]}
|
||||
if draganddrop.grade(submission[0], correct_answer):
|
||||
correct = ['correct']
|
||||
else:
|
||||
correct = ['incorrect']
|
||||
</answer>
|
||||
</customresponse>
|
||||
|
||||
<customresponse>
|
||||
<h3>Drag and Drop with Outline</h3>
|
||||
<p>Label the hydrogen atoms connected with the left carbon atom.</p>
|
||||
<drag_and_drop_input img="https://studio.edx.org/c4x/edX/DemoX/asset/ethglycol.jpg" target_outline="true" one_per_target="true" no_labels="true" label_bg_color="rgb(222, 139, 238)">
|
||||
<draggable id="1" label="Hydrogen" />
|
||||
<draggable id="2" label="Hydrogen" />
|
||||
<target id="t1_o" x="10" y="67" w="100" h="100"/>
|
||||
<target id="t2" x="133" y="3" w="70" h="70"/>
|
||||
<target id="t3" x="2" y="384" w="70" h="70"/>
|
||||
<target id="t4" x="95" y="386" w="70" h="70"/>
|
||||
<target id="t5_c" x="94" y="293" w="91" h="91"/>
|
||||
<target id="t6_c" x="328" y="294" w="91" h="91"/>
|
||||
<target id="t7" x="393" y="463" w="70" h="70"/>
|
||||
<target id="t8" x="344" y="214" w="70" h="70"/>
|
||||
<target id="t9_o" x="445" y="162" w="100" h="100"/>
|
||||
<target id="t10" x="591" y="132" w="70" h="70"/>
|
||||
<problem>
|
||||
<p>In drag and drop problems, students respond to a question by dragging text or objects to a specific location on an image.</p>
|
||||
<p>
|
||||
For more information, see
|
||||
<a href="https://docs.openedx.org/en/latest/educators/references/course_development/exercise_tools/guide_drag_and_drop.html" target="_blank">
|
||||
Drag and Drop Problem (Deprecated)</a> in <i>Building and Running an Open edX Course</i>.
|
||||
</p>
|
||||
<p>
|
||||
When you add the problem, be sure to select <strong>Settings</strong>
|
||||
to specify a <strong>Display Name</strong> and other values that apply.
|
||||
</p>
|
||||
<p>You can use the following example problems as models.</p>
|
||||
<hr/>
|
||||
<customresponse>
|
||||
<h3>Simple Drag and Drop</h3>
|
||||
<p>Drag each word in the scrollbar to the bucket that matches the number of letters in the word.</p>
|
||||
<drag_and_drop_input img="https://studio.edx.org/c4x/edX/DemoX/asset/L9_buckets.png">
|
||||
<draggable id="1" label="a"/>
|
||||
<draggable id="2" label="bog"/>
|
||||
<draggable id="3" label="droll"/>
|
||||
<draggable id="4" label="oboe"/>
|
||||
<draggable id="5" label="swain"/>
|
||||
<draggable id="6" label="in"/>
|
||||
<draggable id="7" label="onyx"/>
|
||||
<draggable id="8" label="of"/>
|
||||
<draggable id="9" label="tap"/>
|
||||
<draggable id="10" label="strop"/>
|
||||
<draggable id="11" label="few"/>
|
||||
</drag_and_drop_input>
|
||||
<answer type="loncapa/python">
|
||||
correct_answer = [{
|
||||
'draggables': ['1', '2'],
|
||||
'targets': ['t2', 't3', 't4' ],
|
||||
'rule':'anyof'
|
||||
}]
|
||||
if draganddrop.grade(submission[0], correct_answer):
|
||||
correct = ['correct']
|
||||
else:
|
||||
correct = ['incorrect']
|
||||
</answer>
|
||||
</customresponse>
|
||||
</problem>
|
||||
<answer type="loncapa/python">
|
||||
correct_answer = {
|
||||
'1': [[70, 150], 121],
|
||||
'6': [[190, 150], 121],
|
||||
'8': [[190, 150], 121],
|
||||
'2': [[310, 150], 121],
|
||||
'9': [[310, 150], 121],
|
||||
'11': [[310, 150], 121],
|
||||
'4': [[420, 150], 121],
|
||||
'7': [[420, 150], 121],
|
||||
'3': [[550, 150], 121],
|
||||
'5': [[550, 150], 121],
|
||||
'10': [[550, 150], 121]}
|
||||
if draganddrop.grade(submission[0], correct_answer):
|
||||
correct = ['correct']
|
||||
else:
|
||||
correct = ['incorrect']
|
||||
</answer>
|
||||
</customresponse>
|
||||
|
||||
<customresponse>
|
||||
<h3>Drag and Drop with Outline</h3>
|
||||
<p>Label the hydrogen atoms connected with the left carbon atom.</p>
|
||||
<drag_and_drop_input img="https://studio.edx.org/c4x/edX/DemoX/asset/ethglycol.jpg" target_outline="true" one_per_target="true" no_labels="true" label_bg_color="rgb(222, 139, 238)">
|
||||
<draggable id="1" label="Hydrogen" />
|
||||
<draggable id="2" label="Hydrogen" />
|
||||
<target id="t1_o" x="10" y="67" w="100" h="100"/>
|
||||
<target id="t2" x="133" y="3" w="70" h="70"/>
|
||||
<target id="t3" x="2" y="384" w="70" h="70"/>
|
||||
<target id="t4" x="95" y="386" w="70" h="70"/>
|
||||
<target id="t5_c" x="94" y="293" w="91" h="91"/>
|
||||
<target id="t6_c" x="328" y="294" w="91" h="91"/>
|
||||
<target id="t7" x="393" y="463" w="70" h="70"/>
|
||||
<target id="t8" x="344" y="214" w="70" h="70"/>
|
||||
<target id="t9_o" x="445" y="162" w="100" h="100"/>
|
||||
<target id="t10" x="591" y="132" w="70" h="70"/>
|
||||
</drag_and_drop_input>
|
||||
<answer type="loncapa/python">
|
||||
correct_answer = [{
|
||||
'draggables': ['1', '2'],
|
||||
'targets': ['t2', 't3', 't4' ],
|
||||
'rule':'anyof'
|
||||
}]
|
||||
if draganddrop.grade(submission[0], correct_answer):
|
||||
correct = ['correct']
|
||||
else:
|
||||
correct = ['incorrect']
|
||||
</answer>
|
||||
</customresponse>
|
||||
</problem>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Math Expression Input
|
||||
markdown: !!null
|
||||
display_name: Math Expression Input
|
||||
markdown: !!null
|
||||
data: |
|
||||
<problem>
|
||||
<formularesponse type="ci" samples="R_1,R_2,R_3@1,2,3:3,4,5#10" answer="R_1*R_2/R_3">
|
||||
<p>You can use this template as a guide to the OLX markup to use for math expression problems. Edit this component to replace the example with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required. Example: Write an expression for the product of R_1, R_2, and the inverse of R_3.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this. Example: To test this example, the correct answer is R_1*R_2/R_3</description>
|
||||
<responseparam type="tolerance" default="0.00001"/>
|
||||
<formulaequationinput size="40"/>
|
||||
</formularesponse>
|
||||
</problem>
|
||||
<problem>
|
||||
<formularesponse type="ci" samples="R_1,R_2,R_3@1,2,3:3,4,5#10" answer="R_1*R_2/R_3">
|
||||
<p>You can use this template as a guide to the OLX markup to use for math expression problems. Edit this component to replace the example with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required. Example: Write an expression for the product of R_1, R_2, and the inverse of R_3.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this. Example: To test this example, the correct answer is R_1*R_2/R_3</description>
|
||||
<responseparam type="tolerance" default="0.00001"/>
|
||||
<formulaequationinput size="40"/>
|
||||
</formularesponse>
|
||||
</problem>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Image Mapped Input
|
||||
markdown: !!null
|
||||
data: |
|
||||
<problem>
|
||||
<p>
|
||||
In an image mapped input problem, also known as a "pointing on a picture" problem, students click inside a defined region in an image. You define this region by including coordinates in the body of the problem. You can define one rectangular region,
|
||||
multiple rectangular regions, or one non-rectangular region. For more information, see
|
||||
<a href="https://docs.openedx.org/en/latest/educators/concepts/exercise_tools/about_image_mapped_input.html" target="_blank">Image Mapped Input Problem</a>
|
||||
in
|
||||
<i>Building and Running an Open edX Course</i>.
|
||||
</p>
|
||||
<p>When you add the problem, be sure to select
|
||||
<strong>Settings</strong>
|
||||
to specify a
|
||||
<strong>Display Name</strong>
|
||||
and other values that apply.</p>
|
||||
<p>You can use the following example problem as a model.</p>
|
||||
<imageresponse>
|
||||
<p>What country is home to the Great Pyramid of Giza as well as the cities of Cairo and Memphis? Click the country on the map below.</p>
|
||||
<imageinput src="https://studio.edx.org/c4x/edX/DemoX/asset/Africa.png" width="600" height="638" rectangle="(338,98)-(412,168)" alt="Map of Africa"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Egypt is home to not only the Pyramids, Cairo, and Memphis, but also the Sphinx and the ancient Royal Library of Alexandria.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</imageresponse>
|
||||
</problem>
|
||||
display_name: Image Mapped Input
|
||||
markdown: !!null
|
||||
data: |
|
||||
<problem>
|
||||
<p>
|
||||
In an image mapped input problem, also known as a "pointing on a picture" problem, students click inside a defined region in an image. You define this region by including coordinates in the body of the problem. You can define one rectangular region,
|
||||
multiple rectangular regions, or one non-rectangular region. For more information, see
|
||||
<a href="https://docs.openedx.org/en/latest/educators/concepts/exercise_tools/about_image_mapped_input.html" target="_blank">Image Mapped Input Problem</a>
|
||||
in
|
||||
<i>Building and Running an Open edX Course</i>.
|
||||
</p>
|
||||
<p>When you add the problem, be sure to select
|
||||
<strong>Settings</strong>
|
||||
to specify a
|
||||
<strong>Display Name</strong>
|
||||
and other values that apply.</p>
|
||||
<p>You can use the following example problem as a model.</p>
|
||||
<imageresponse>
|
||||
<p>What country is home to the Great Pyramid of Giza as well as the cities of Cairo and Memphis? Click the country on the map below.</p>
|
||||
<imageinput src="https://studio.edx.org/c4x/edX/DemoX/asset/Africa.png" width="600" height="638" rectangle="(338,98)-(412,168)" alt="Map of Africa"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Egypt is home to not only the Pyramids, Cairo, and Memphis, but also the Sphinx and the ancient Royal Library of Alexandria.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</imageresponse>
|
||||
</problem>
|
||||
|
||||
@@ -1,72 +1,72 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Custom JavaScript Display and Grading
|
||||
markdown: !!null
|
||||
showanswer: never
|
||||
display_name: Custom JavaScript Display and Grading
|
||||
markdown: !!null
|
||||
showanswer: never
|
||||
data: |
|
||||
<problem>
|
||||
<p>
|
||||
In these problems (also called custom JavaScript problems or JS Input
|
||||
problems), you add a problem or tool that uses JavaScript in Studio.
|
||||
Studio embeds the problem in an IFrame so that your learners can
|
||||
interact with it in the LMS. You can grade your learners' work using
|
||||
JavaScript and some basic Python, and the grading is integrated into the
|
||||
edX grading system.
|
||||
</p>
|
||||
<p>
|
||||
The JS Input problem that you create must use HTML, JavaScript, and
|
||||
cascading style sheets (CSS). You can use any application creation tool,
|
||||
such as the Google Web Toolkit (GWT), to create your JS Input problem.
|
||||
</p>
|
||||
<p>
|
||||
For more information, see
|
||||
<a href="https://docs.openedx.org/en/latest/educators/references/course_development/exercise_tools/custom_javascript.html" target="_blank">
|
||||
Custom JavaScript Problem</a> in <i>Building and Running an Open edX Course</i>.
|
||||
</p>
|
||||
<p>
|
||||
When you add the problem, be sure to select <strong>Settings</strong>
|
||||
to specify a <strong>Display Name</strong> and other values that apply.
|
||||
Also, be sure to specify a <strong>title</strong> attribute on the <strong>jsinput</strong> tag;
|
||||
this title is used for the title attribute on the generated IFrame. Generally,
|
||||
the title attribute on the IFrame should match the title tag of the HTML hosted
|
||||
within the IFrame, which is specified by the <strong>html_file</strong> attribute.
|
||||
</p>
|
||||
<p>You can use the following example problem as a model.</p>
|
||||
<problem>
|
||||
<p>
|
||||
In these problems (also called custom JavaScript problems or JS Input
|
||||
problems), you add a problem or tool that uses JavaScript in Studio.
|
||||
Studio embeds the problem in an IFrame so that your learners can
|
||||
interact with it in the LMS. You can grade your learners' work using
|
||||
JavaScript and some basic Python, and the grading is integrated into the
|
||||
edX grading system.
|
||||
</p>
|
||||
<p>
|
||||
The JS Input problem that you create must use HTML, JavaScript, and
|
||||
cascading style sheets (CSS). You can use any application creation tool,
|
||||
such as the Google Web Toolkit (GWT), to create your JS Input problem.
|
||||
</p>
|
||||
<p>
|
||||
For more information, see
|
||||
<a href="https://docs.openedx.org/en/latest/educators/references/course_development/exercise_tools/custom_javascript.html" target="_blank">
|
||||
Custom JavaScript Problem</a> in <i>Building and Running an Open edX Course</i>.
|
||||
</p>
|
||||
<p>
|
||||
When you add the problem, be sure to select <strong>Settings</strong>
|
||||
to specify a <strong>Display Name</strong> and other values that apply.
|
||||
Also, be sure to specify a <strong>title</strong> attribute on the <strong>jsinput</strong> tag;
|
||||
this title is used for the title attribute on the generated IFrame. Generally,
|
||||
the title attribute on the IFrame should match the title tag of the HTML hosted
|
||||
within the IFrame, which is specified by the <strong>html_file</strong> attribute.
|
||||
</p>
|
||||
<p>You can use the following example problem as a model.</p>
|
||||
|
||||
<customresponse cfn="check_function">
|
||||
<script type="loncapa/python">
|
||||
<![CDATA[
|
||||
import json
|
||||
def check_function(e, ans):
|
||||
"""
|
||||
"response" is a dictionary that contains two keys, "answer" and "state".
|
||||
The value of "answer" is the JSON string that "getGrade" returns.
|
||||
The value of "state" is the JSON string that "getState" returns.
|
||||
Clicking either "Submit" or "Save" registers the current state.
|
||||
"""
|
||||
response = json.loads(ans)
|
||||
<customresponse cfn="check_function">
|
||||
<script type="loncapa/python">
|
||||
<![CDATA[
|
||||
import json
|
||||
def check_function(e, ans):
|
||||
"""
|
||||
"response" is a dictionary that contains two keys, "answer" and "state".
|
||||
The value of "answer" is the JSON string that "getGrade" returns.
|
||||
The value of "state" is the JSON string that "getState" returns.
|
||||
Clicking either "Submit" or "Save" registers the current state.
|
||||
"""
|
||||
response = json.loads(ans)
|
||||
|
||||
# You can use the value of the answer key to grade:
|
||||
answer = json.loads(response["answer"])
|
||||
return answer == "correct"
|
||||
# You can use the value of the answer key to grade:
|
||||
answer = json.loads(response["answer"])
|
||||
return answer == "correct"
|
||||
|
||||
# Or you can use the value of the state key to grade:
|
||||
"""
|
||||
state = json.loads(response["state"])
|
||||
return state["selectedChoice"] == "correct"
|
||||
"""
|
||||
]]>
|
||||
</script>
|
||||
<p>This is paragraph text displayed before the IFrame.</p>
|
||||
<jsinput
|
||||
gradefn="JSInputDemo.getGrade"
|
||||
get_statefn="JSInputDemo.getState"
|
||||
set_statefn="JSInputDemo.setState"
|
||||
initial_state='{"selectedChoice": "incorrect1", "availableChoices": ["incorrect1", "correct", "incorrect2"]}'
|
||||
width="600"
|
||||
height="100"
|
||||
html_file="https://files.edx.org/custom-js-example/jsinput_example.html"
|
||||
title="Dropdown with Dynamic Text"
|
||||
sop="false"/>
|
||||
</customresponse>
|
||||
</problem>
|
||||
# Or you can use the value of the state key to grade:
|
||||
"""
|
||||
state = json.loads(response["state"])
|
||||
return state["selectedChoice"] == "correct"
|
||||
"""
|
||||
]]>
|
||||
</script>
|
||||
<p>This is paragraph text displayed before the IFrame.</p>
|
||||
<jsinput
|
||||
gradefn="JSInputDemo.getGrade"
|
||||
get_statefn="JSInputDemo.getState"
|
||||
set_statefn="JSInputDemo.setState"
|
||||
initial_state='{"selectedChoice": "incorrect1", "availableChoices": ["incorrect1", "correct", "incorrect2"]}'
|
||||
width="600"
|
||||
height="100"
|
||||
html_file="https://files.edx.org/custom-js-example/jsinput_example.html"
|
||||
title="Dropdown with Dynamic Text"
|
||||
sop="false"/>
|
||||
</customresponse>
|
||||
</problem>
|
||||
|
||||
@@ -1,247 +1,247 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Problem Written in LaTeX
|
||||
source_code: |
|
||||
% Nearly any kind of edX problem can be authored using LaTeX as
|
||||
% the source language. Write LaTeX as usual, including equations. The
|
||||
% key feature is the \edXabox{} macro, which specifies an "answer
|
||||
% box" that queries students for a response, and specifies what the
|
||||
% expected (correct) answer is.
|
||||
display_name: Problem Written in LaTeX
|
||||
source_code: |
|
||||
% Nearly any kind of edX problem can be authored using LaTeX as
|
||||
% the source language. Write LaTeX as usual, including equations. The
|
||||
% key feature is the \edXabox{} macro, which specifies an "answer
|
||||
% box" that queries students for a response, and specifies what the
|
||||
% expected (correct) answer is.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example "option" problem}
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example "option" problem}
|
||||
|
||||
Which of the following countries celebrates its independence on August 15?
|
||||
Which of the following countries celebrates its independence on August 15?
|
||||
|
||||
\edXabox{options='India','Spain','China','Bermuda' expect='India'}
|
||||
\edXabox{options='India','Spain','China','Bermuda' expect='India'}
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example "multiple choice" problem}
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example "multiple choice" problem}
|
||||
|
||||
Which of the following countries has the largest population?
|
||||
Which of the following countries has the largest population?
|
||||
|
||||
\edXabox{ type="multichoice" expect="Indonesia" options="Brazil","Germany","Indonesia","Russia" }
|
||||
\edXabox{ type="multichoice" expect="Indonesia" options="Brazil","Germany","Indonesia","Russia" }
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example "symbolic" problem}
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example "symbolic" problem}
|
||||
|
||||
What is Einstein's equation for the energy equivalent of a mass $m$?
|
||||
What is Einstein's equation for the energy equivalent of a mass $m$?
|
||||
|
||||
\edXabox{type='symbolic' size='90' expect='m*c^2' }
|
||||
\edXabox{type='symbolic' size='90' expect='m*c^2' }
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example "numerical" problem}
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example "numerical" problem}
|
||||
|
||||
Estimate the energy savings (in J/y) if all the people
|
||||
($3\times 10^8$) in the U.~S. switched from U.~S. code to low-flow
|
||||
shower heads.
|
||||
Estimate the energy savings (in J/y) if all the people
|
||||
($3\times 10^8$) in the U.~S. switched from U.~S. code to low-flow
|
||||
shower heads.
|
||||
|
||||
\edXinline{Energy saved = }\edXabox{expect="0.52" type="numerical" tolerance='0.02' inline='1' } %
|
||||
\edXinline{~EJ/year}
|
||||
\edXinline{Energy saved = }\edXabox{expect="0.52" type="numerical" tolerance='0.02' inline='1' } %
|
||||
\edXinline{~EJ/year}
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example "string response" problem}
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example "string response" problem}
|
||||
|
||||
What was the first post-secondary school in China to allow both male and female students?
|
||||
What was the first post-secondary school in China to allow both male and female students?
|
||||
|
||||
\edXabox{ type="string" expect="Nanjing Higher Normal Institute" options="ci" }
|
||||
\edXabox{ type="string" expect="Nanjing Higher Normal Institute" options="ci" }
|
||||
|
||||
You can include an explanation of the answer by using the edXsolution
|
||||
macro. Click "Show Answer" to see the explanation.
|
||||
You can include an explanation of the answer by using the edXsolution
|
||||
macro. Click "Show Answer" to see the explanation.
|
||||
|
||||
\begin{edXsolution}
|
||||
Nanjing Higher Normal Institute first admitted female students in 1920.
|
||||
\end{edXsolution}
|
||||
\begin{edXsolution}
|
||||
Nanjing Higher Normal Institute first admitted female students in 1920.
|
||||
\end{edXsolution}
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example "custom response" problem}
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example "custom response" problem}
|
||||
|
||||
In this problem, a custom Python script determines if the answer is correct.
|
||||
In this problem, a custom Python script determines if the answer is correct.
|
||||
|
||||
\begin{edXscript}
|
||||
def sumtest(expect,ans):
|
||||
(a1,a2) = map(float,eval(ans))
|
||||
return (a1+a2)==10
|
||||
\end{edXscript}
|
||||
\begin{edXscript}
|
||||
def sumtest(expect,ans):
|
||||
(a1,a2) = map(float,eval(ans))
|
||||
return (a1+a2)==10
|
||||
\end{edXscript}
|
||||
|
||||
Enter a Python list of two numbers that sum to 10. For example, your answer might be [9,1] or [4,6].
|
||||
Enter a Python list of two numbers that sum to 10. For example, your answer might be [9,1] or [4,6].
|
||||
|
||||
\edXabox{expect="[1,9]" type="custom" cfn="sumtest"}
|
||||
\edXabox{expect="[1,9]" type="custom" cfn="sumtest"}
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example image}
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example image}
|
||||
|
||||
Include an image by using the following macro:
|
||||
Include an image by using the following macro:
|
||||
|
||||
\edXxml{<img src="https://courses.edx.org/static/images/placeholder-image.png"/>}
|
||||
\edXxml{<img src="https://courses.edx.org/static/images/placeholder-image.png"/>}
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example show/hide explanation}
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
\subsection{Example show/hide explanation}
|
||||
|
||||
You can provide additional information that only appears at certain times by including a "showhide" flag.
|
||||
You can provide additional information that only appears at certain times by including a "showhide" flag.
|
||||
|
||||
\edXshowhide{sh1}{More explanation}{This is a hidden explanation. It
|
||||
can contain equations, such as $\alpha = \frac{2}{\sqrt{1+\gamma}}$ }.
|
||||
\edXshowhide{sh1}{More explanation}{This is a hidden explanation. It
|
||||
can contain equations, such as $\alpha = \frac{2}{\sqrt{1+\gamma}}$ }.
|
||||
|
||||
This is additional text after the hidden explanation.
|
||||
markdown: !!null
|
||||
This is additional text after the hidden explanation.
|
||||
markdown: !!null
|
||||
|
||||
data: |
|
||||
<?xml version="1.0"?>
|
||||
<problem>
|
||||
<?xml version="1.0"?>
|
||||
<problem>
|
||||
<p>
|
||||
If you have a problem that is already written in LaTeX, you can use this problem type to
|
||||
easily convert your code into XML. After you paste your code into the LaTeX editor,
|
||||
you only need to make a few minor adjustments.
|
||||
</p>
|
||||
<p>
|
||||
For more information, see
|
||||
<a href="https://docs.openedx.org/en/latest/educators/how-tos/course_development/exercise_tools/create_problem_in_latex.html" target="_blank">
|
||||
Problem Written in LaTeX</a> in <i>Building and Running an Open edX Course</i>.
|
||||
</p>
|
||||
<p>You can use the following example problems as models.</p>
|
||||
<p><strong>Example Option Problem</strong></p>
|
||||
<br/>
|
||||
<optionresponse>
|
||||
<label>Which of the following countries celebrates its independence on August 15?</label>
|
||||
<optioninput options="('India','Spain','China','Bermuda')" correct="India"></optioninput>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>India became an independent nation on August 15, 1947.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</optionresponse>
|
||||
<br/>
|
||||
|
||||
<p><strong>Example Multiple Choice Problem</strong></p>
|
||||
<multiplechoiceresponse>
|
||||
<label>Which of the following countries has the largest population?</label>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false" name="brazil">Brazil</choice>
|
||||
<choice correct="false" name="germany">Germany</choice>
|
||||
<choice correct="true" name="indonesia">Indonesia</choice>
|
||||
<choice correct="false" name="russia">Russia</choice>
|
||||
</choicegroup>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>According to September 2014 estimates:</p>
|
||||
<p>The population of Indonesia is approximately 250 million.</p>
|
||||
<p>The population of Brazil is approximately 200 million.</p>
|
||||
<p>The population of Russia is approximately 146 million.</p>
|
||||
<p>The population of Germany is approximately 81 million.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</multiplechoiceresponse>
|
||||
<br/>
|
||||
|
||||
<p><strong>Example Math Expression Problem</strong></p>
|
||||
<symbolicresponse expect="m*c^2">
|
||||
<p>What is Einstein's equation for the energy equivalent of a mass [mathjaxinline]m[/mathjaxinline]?</p>
|
||||
<textline size="90" correct_answer="m*c^2" math="1"/>
|
||||
</symbolicresponse>
|
||||
<br/>
|
||||
|
||||
<p><strong>Example Numerical Problem</strong></p>
|
||||
<numericalresponse inline="1" answer="0.52">
|
||||
<label>Estimate the energy savings (in J/y) if all the people ([mathjaxinline]3\times 10^8[/mathjaxinline]) in the U. S. switched from U. S. code to low-flow shower heads.</label>
|
||||
<formulaequationinput trailing_text="EJ/year" />
|
||||
<responseparam type="tolerance" default="0.02"/>
|
||||
</numericalresponse>
|
||||
<br/>
|
||||
|
||||
<p><strong>Example Fill-in-the-Blank Problem</strong></p>
|
||||
<stringresponse answer="Nanjing Higher Normal Institute" type="ci" >
|
||||
<label>What was the first post-secondary school in China to allow both male and female students?</label>
|
||||
<additional_answer>National Central University</additional_answer>
|
||||
<additional_answer>Nanjing University</additional_answer>
|
||||
<textline size="40"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Nanjing Higher Normal Institute first admitted female students in 1920.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</stringresponse>
|
||||
<br/>
|
||||
|
||||
<p><strong>Example Custom Python-Evaluated Input Problem</strong></p>
|
||||
|
||||
<customresponse cfn="test_add_to_ten">
|
||||
<script type="loncapa/python">
|
||||
def test_add_to_ten(expect, ans):
|
||||
return test_add(10, ans)
|
||||
</script>
|
||||
<p>Enter two integers that sum to 10.</p>
|
||||
<textline size="40" correct_answer="3"/><br/>
|
||||
<textline size="40" correct_answer="7"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Any set of integers on the line \(y = 10 - x\) satisfy these constraints.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</customresponse>
|
||||
|
||||
<customresponse cfn="test_add" expect="20">
|
||||
<script type="loncapa/python">
|
||||
def test_add(expect, ans):
|
||||
try:
|
||||
a1=int(ans[0])
|
||||
a2=int(ans[1])
|
||||
return (a1+a2) == int(expect)
|
||||
except ValueError:
|
||||
return False
|
||||
</script>
|
||||
<p>Enter two integers that sum to 20.</p>
|
||||
<textline size="40" correct_answer="11"/><br/>
|
||||
<textline size="40" correct_answer="9"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Any set of integers on the line \(y = 20 - x\) satisfy these constraints.</p>
|
||||
<p>To add an image to the solution, use an HTML "img" tag. Make sure to include alt text.</p>
|
||||
<img src="/static/images/placeholder-image.png" width="400" alt="Description of image"/>
|
||||
</div>
|
||||
</solution>
|
||||
</customresponse>
|
||||
<br/>
|
||||
|
||||
<p><strong>Example Image Mapped Input Problem</strong></p>
|
||||
<imageresponse>
|
||||
<p>
|
||||
If you have a problem that is already written in LaTeX, you can use this problem type to
|
||||
easily convert your code into XML. After you paste your code into the LaTeX editor,
|
||||
you only need to make a few minor adjustments.
|
||||
What country is home to the Great Pyramid of Giza as well as the cities
|
||||
of Cairo and Memphis? Click the country on the map below.
|
||||
</p>
|
||||
<p>
|
||||
For more information, see
|
||||
<a href="https://docs.openedx.org/en/latest/educators/how-tos/course_development/exercise_tools/create_problem_in_latex.html" target="_blank">
|
||||
Problem Written in LaTeX</a> in <i>Building and Running an Open edX Course</i>.
|
||||
</p>
|
||||
<p>You can use the following example problems as models.</p>
|
||||
<p><strong>Example Option Problem</strong></p>
|
||||
<br/>
|
||||
<optionresponse>
|
||||
<label>Which of the following countries celebrates its independence on August 15?</label>
|
||||
<optioninput options="('India','Spain','China','Bermuda')" correct="India"></optioninput>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>India became an independent nation on August 15, 1947.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</optionresponse>
|
||||
<br/>
|
||||
<imageinput src="https://studio.edx.org/c4x/edX/DemoX/asset/Africa.png"
|
||||
width="600" height="638" rectangle="(338,98)-(412,168)" alt="Map of
|
||||
Africa"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Egypt is home to not only the Pyramids, Cairo, and Memphis, but also
|
||||
the Sphinx and the ancient Royal Library of Alexandria.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</imageresponse>
|
||||
<br/>
|
||||
|
||||
<p><strong>Example Multiple Choice Problem</strong></p>
|
||||
<multiplechoiceresponse>
|
||||
<label>Which of the following countries has the largest population?</label>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false" name="brazil">Brazil</choice>
|
||||
<choice correct="false" name="germany">Germany</choice>
|
||||
<choice correct="true" name="indonesia">Indonesia</choice>
|
||||
<choice correct="false" name="russia">Russia</choice>
|
||||
</choicegroup>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>According to September 2014 estimates:</p>
|
||||
<p>The population of Indonesia is approximately 250 million.</p>
|
||||
<p>The population of Brazil is approximately 200 million.</p>
|
||||
<p>The population of Russia is approximately 146 million.</p>
|
||||
<p>The population of Germany is approximately 81 million.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</multiplechoiceresponse>
|
||||
<br/>
|
||||
|
||||
<p><strong>Example Math Expression Problem</strong></p>
|
||||
<symbolicresponse expect="m*c^2">
|
||||
<p>What is Einstein's equation for the energy equivalent of a mass [mathjaxinline]m[/mathjaxinline]?</p>
|
||||
<textline size="90" correct_answer="m*c^2" math="1"/>
|
||||
</symbolicresponse>
|
||||
<br/>
|
||||
|
||||
<p><strong>Example Numerical Problem</strong></p>
|
||||
<numericalresponse inline="1" answer="0.52">
|
||||
<label>Estimate the energy savings (in J/y) if all the people ([mathjaxinline]3\times 10^8[/mathjaxinline]) in the U. S. switched from U. S. code to low-flow shower heads.</label>
|
||||
<formulaequationinput trailing_text="EJ/year" />
|
||||
<responseparam type="tolerance" default="0.02"/>
|
||||
</numericalresponse>
|
||||
<br/>
|
||||
|
||||
<p><strong>Example Fill-in-the-Blank Problem</strong></p>
|
||||
<stringresponse answer="Nanjing Higher Normal Institute" type="ci" >
|
||||
<label>What was the first post-secondary school in China to allow both male and female students?</label>
|
||||
<additional_answer>National Central University</additional_answer>
|
||||
<additional_answer>Nanjing University</additional_answer>
|
||||
<textline size="40"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Nanjing Higher Normal Institute first admitted female students in 1920.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</stringresponse>
|
||||
<br/>
|
||||
|
||||
<p><strong>Example Custom Python-Evaluated Input Problem</strong></p>
|
||||
|
||||
<customresponse cfn="test_add_to_ten">
|
||||
<script type="loncapa/python">
|
||||
def test_add_to_ten(expect, ans):
|
||||
return test_add(10, ans)
|
||||
</script>
|
||||
<p>Enter two integers that sum to 10.</p>
|
||||
<textline size="40" correct_answer="3"/><br/>
|
||||
<textline size="40" correct_answer="7"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Any set of integers on the line \(y = 10 - x\) satisfy these constraints.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</customresponse>
|
||||
|
||||
<customresponse cfn="test_add" expect="20">
|
||||
<script type="loncapa/python">
|
||||
def test_add(expect, ans):
|
||||
try:
|
||||
a1=int(ans[0])
|
||||
a2=int(ans[1])
|
||||
return (a1+a2) == int(expect)
|
||||
except ValueError:
|
||||
return False
|
||||
</script>
|
||||
<p>Enter two integers that sum to 20.</p>
|
||||
<textline size="40" correct_answer="11"/><br/>
|
||||
<textline size="40" correct_answer="9"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Any set of integers on the line \(y = 20 - x\) satisfy these constraints.</p>
|
||||
<p>To add an image to the solution, use an HTML "img" tag. Make sure to include alt text.</p>
|
||||
<img src="/static/images/placeholder-image.png" width="400" alt="Description of image"/>
|
||||
</div>
|
||||
</solution>
|
||||
</customresponse>
|
||||
<br/>
|
||||
|
||||
<p><strong>Example Image Mapped Input Problem</strong></p>
|
||||
<imageresponse>
|
||||
<p>
|
||||
What country is home to the Great Pyramid of Giza as well as the cities
|
||||
of Cairo and Memphis? Click the country on the map below.
|
||||
</p>
|
||||
<imageinput src="https://studio.edx.org/c4x/edX/DemoX/asset/Africa.png"
|
||||
width="600" height="638" rectangle="(338,98)-(412,168)" alt="Map of
|
||||
Africa"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Egypt is home to not only the Pyramids, Cairo, and Memphis, but also
|
||||
the Sphinx and the ancient Royal Library of Alexandria.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</imageresponse>
|
||||
<br/>
|
||||
|
||||
<p><strong>Example Hidden Explanation</strong></p>
|
||||
<p>You can provide additional information that only appears at certain times by including a "showhide" flag. </p>
|
||||
<p>
|
||||
<table class="wikitable collapsible collapsed">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th> More Information [<a href="javascript:$('#sh1').toggle()" id="sh1l">show</a>]</th>
|
||||
</tr>
|
||||
<tr id="sh1" style="display:none">
|
||||
<td>
|
||||
<p>This is a hidden explanation. It can contain equations, such as [mathjaxinline]\alpha = \frac{2}{\sqrt {1+\gamma }}[/mathjaxinline]. </p>
|
||||
<p>This is additional text after the hidden explanation. </p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</p>
|
||||
</problem>
|
||||
<p><strong>Example Hidden Explanation</strong></p>
|
||||
<p>You can provide additional information that only appears at certain times by including a "showhide" flag. </p>
|
||||
<p>
|
||||
<table class="wikitable collapsible collapsed">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th> More Information [<a href="javascript:$('#sh1').toggle()" id="sh1l">show</a>]</th>
|
||||
</tr>
|
||||
<tr id="sh1" style="display:none">
|
||||
<td>
|
||||
<p>This is a hidden explanation. It can contain equations, such as [mathjaxinline]\alpha = \frac{2}{\sqrt {1+\gamma }}[/mathjaxinline]. </p>
|
||||
<p>This is additional text after the hidden explanation. </p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</p>
|
||||
</problem>
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Multiple Choice
|
||||
markdown: |
|
||||
display_name: Multiple Choice
|
||||
markdown: |
|
||||
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice problems. Edit this component to replace this template with your own assessment.
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice problems. Edit this component to replace this template with your own assessment.
|
||||
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
|
||||
( ) an incorrect answer
|
||||
(x) the correct answer
|
||||
( ) an incorrect answer
|
||||
( ) an incorrect answer
|
||||
(x) the correct answer
|
||||
( ) an incorrect answer
|
||||
|
||||
|
||||
data: |
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<choicegroup>
|
||||
<choice correct="false">an incorrect answer</choice>
|
||||
<choice correct="true">the correct answer</choice>
|
||||
<choice correct="false">an incorrect answer</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>
|
||||
data: |
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<choicegroup>
|
||||
<choice correct="false">an incorrect answer</choice>
|
||||
<choice correct="true">the correct answer</choice>
|
||||
<choice correct="false">an incorrect answer</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Multiple Choice with Hints and Feedback
|
||||
markdown: |
|
||||
display_name: Multiple Choice with Hints and Feedback
|
||||
markdown: |
|
||||
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice with hints and feedback problems. Edit this component to replace this template with your own assessment.
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice with hints and feedback problems. Edit this component to replace this template with your own assessment.
|
||||
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
|
||||
( ) an incorrect answer {{You can specify optional feedback like this, which appears after this answer is submitted.}}
|
||||
(x) the correct answer
|
||||
( ) an incorrect answer {{You can specify optional feedback for none, a subset, or all of the answers.}}
|
||||
( ) an incorrect answer {{You can specify optional feedback like this, which appears after this answer is submitted.}}
|
||||
(x) the correct answer
|
||||
( ) an incorrect answer {{You can specify optional feedback for none, a subset, or all of the answers.}}
|
||||
|
||||
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
|
||||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
|
||||
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
|
||||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
|
||||
|
||||
hinted: true
|
||||
data: |
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<choicegroup>
|
||||
<choice correct="false">an incorrect answer
|
||||
<choicehint>You can specify optional feedback like this, which appears after this answer is submitted.</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">the correct answer
|
||||
</choice>
|
||||
<choice correct="false">an incorrect answer
|
||||
<choicehint>You can specify optional feedback for none, a subset, or all of the answers.</choicehint>
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
data: |
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<choicegroup>
|
||||
<choice correct="false">an incorrect answer
|
||||
<choicehint>You can specify optional feedback like this, which appears after this answer is submitted.</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">the correct answer
|
||||
</choice>
|
||||
<choice correct="false">an incorrect answer
|
||||
<choicehint>You can specify optional feedback for none, a subset, or all of the answers.</choicehint>
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
|
||||
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
<demandhint>
|
||||
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
|
||||
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Numerical Input
|
||||
markdown: |
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input problems. Edit this component to replace this template with your own assessment.
|
||||
display_name: Numerical Input
|
||||
markdown: |
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input problems. Edit this component to replace this template with your own assessment.
|
||||
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
|
||||
= 100 +-5
|
||||
= 100 +-5
|
||||
|
||||
data: |
|
||||
<problem>
|
||||
<numericalresponse answer="100">
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<responseparam type="tolerance" default="5"/>
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
</problem>
|
||||
data: |
|
||||
<problem>
|
||||
<numericalresponse answer="100">
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<responseparam type="tolerance" default="5"/>
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
</problem>
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Numerical Input with Hints and Feedback
|
||||
markdown: |
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input with hints and feedback problems. Edit this component to replace this template with your own assessment.
|
||||
display_name: Numerical Input with Hints and Feedback
|
||||
markdown: |
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input with hints and feedback problems. Edit this component to replace this template with your own assessment.
|
||||
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
|
||||
= 100 +-5 {{You can specify optional feedback like this, which appears after this answer is submitted.}}
|
||||
= 100 +-5 {{You can specify optional feedback like this, which appears after this answer is submitted.}}
|
||||
|
||||
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
|
||||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
|
||||
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
|
||||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
|
||||
|
||||
hinted: true
|
||||
data: |
|
||||
<problem>
|
||||
<numericalresponse answer="100">
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<responseparam type="tolerance" default="5"/>
|
||||
<formulaequationinput/>
|
||||
<correcthint>You can specify optional feedback like this, which appears after this answer is submitted.</correcthint>
|
||||
</numericalresponse>
|
||||
<demandhint>
|
||||
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
|
||||
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
data: |
|
||||
<problem>
|
||||
<numericalresponse answer="100">
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<responseparam type="tolerance" default="5"/>
|
||||
<formulaequationinput/>
|
||||
<correcthint>You can specify optional feedback like this, which appears after this answer is submitted.</correcthint>
|
||||
</numericalresponse>
|
||||
<demandhint>
|
||||
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
|
||||
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Dropdown
|
||||
markdown: |
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown problems. Edit this component to replace this template with your own assessment.
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
[[
|
||||
an incorrect answer
|
||||
(the correct answer)
|
||||
an incorrect answer
|
||||
]]
|
||||
data: |
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this. </description>
|
||||
<optioninput>
|
||||
<option correct="False">an incorrect answer</option>
|
||||
<option correct="True">the correct answer</option>
|
||||
<option correct="False">an incorrect answer</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
</problem>
|
||||
display_name: Dropdown
|
||||
markdown: |
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown problems. Edit this component to replace this template with your own assessment.
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
[[
|
||||
an incorrect answer
|
||||
(the correct answer)
|
||||
an incorrect answer
|
||||
]]
|
||||
data: |
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this. </description>
|
||||
<optioninput>
|
||||
<option correct="False">an incorrect answer</option>
|
||||
<option correct="True">the correct answer</option>
|
||||
<option correct="False">an incorrect answer</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
</problem>
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Dropdown with Hints and Feedback
|
||||
markdown: |
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown with hints and feedback problems. Edit this component to replace this template with your own assessment.
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
[[
|
||||
an incorrect answer {{You can specify optional feedback like this, which appears after this answer is submitted.}}
|
||||
(the correct answer)
|
||||
an incorrect answer {{You can specify optional feedback for none, a subset, or all of the answers.}}
|
||||
]]
|
||||
display_name: Dropdown with Hints and Feedback
|
||||
markdown: |
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown with hints and feedback problems. Edit this component to replace this template with your own assessment.
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
[[
|
||||
an incorrect answer {{You can specify optional feedback like this, which appears after this answer is submitted.}}
|
||||
(the correct answer)
|
||||
an incorrect answer {{You can specify optional feedback for none, a subset, or all of the answers.}}
|
||||
]]
|
||||
|
||||
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
|
||||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
|
||||
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
|
||||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
|
||||
|
||||
hinted: true
|
||||
data: |
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this. </description>
|
||||
<optioninput>
|
||||
<option correct="False">an incorrect answer <optionhint>You can specify optional feedback like this, which appears after this answer is submitted.</optionhint></option>
|
||||
<option correct="True">the correct answer</option>
|
||||
<option correct="False">an incorrect answer <optionhint>You can specify optional feedback for none, a subset, or all of the answers.</optionhint></option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
<demandhint>
|
||||
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
|
||||
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
data: |
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this. </description>
|
||||
<optioninput>
|
||||
<option correct="False">an incorrect answer <optionhint>You can specify optional feedback like this, which appears after this answer is submitted.</optionhint></option>
|
||||
<option correct="True">the correct answer</option>
|
||||
<option correct="False">an incorrect answer <optionhint>You can specify optional feedback for none, a subset, or all of the answers.</optionhint></option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
<demandhint>
|
||||
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
|
||||
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Problem with Adaptive Hint
|
||||
markdown: !!null
|
||||
display_name: Problem with Adaptive Hint
|
||||
markdown: !!null
|
||||
data: |
|
||||
<problem>
|
||||
<text>
|
||||
<p><h4>Problem With Adaptive Hint</h4></p>
|
||||
<p>This problem demonstrates a question with hints, based on using the <tt class="tt">hintfn</tt> method. </p>
|
||||
<problem>
|
||||
<text>
|
||||
<p><h4>Problem With Adaptive Hint</h4></p>
|
||||
<p>This problem demonstrates a question with hints, based on using the <tt class="tt">hintfn</tt> method. </p>
|
||||
|
||||
<customresponse cfn="test_str" expect="python">
|
||||
<script type="text/python" system_path="python_lib">
|
||||
def test_str(expect, ans):
|
||||
print(expect, ans)
|
||||
ans = ans.strip("'")
|
||||
ans = ans.strip('"')
|
||||
return expect == ans.lower()
|
||||
<customresponse cfn="test_str" expect="python">
|
||||
<script type="text/python" system_path="python_lib">
|
||||
def test_str(expect, ans):
|
||||
print(expect, ans)
|
||||
ans = ans.strip("'")
|
||||
ans = ans.strip('"')
|
||||
return expect == ans.lower()
|
||||
|
||||
def hint_fn(answer_ids, student_answers, new_cmap, old_cmap):
|
||||
aid = answer_ids[0]
|
||||
ans = str(student_answers[aid]).lower()
|
||||
print('hint_fn called, ans=', ans)
|
||||
hint = ''
|
||||
if 'java' in ans:
|
||||
hint = 'that is only good for drinking'
|
||||
elif 'perl' in ans:
|
||||
hint = 'not that rich'
|
||||
elif 'pascal' in ans:
|
||||
hint = 'that is a beatnick language'
|
||||
elif 'fortran' in ans:
|
||||
hint = 'those were the good days'
|
||||
elif 'clu' in ans:
|
||||
hint = 'you must be invariant'
|
||||
if hint:
|
||||
hint = "<font color='blue'>Hint: {0}</font>".format(hint)
|
||||
new_cmap.set_hint_and_mode(aid,hint,'always')
|
||||
</script>
|
||||
<label>What is the best programming language that exists today? You may enter your answer in upper or lower case, with or without quotes.</label>
|
||||
<textline correct_answer="python"/>
|
||||
<hintgroup hintfn="hint_fn"/>
|
||||
</customresponse>
|
||||
</text>
|
||||
</problem>
|
||||
def hint_fn(answer_ids, student_answers, new_cmap, old_cmap):
|
||||
aid = answer_ids[0]
|
||||
ans = str(student_answers[aid]).lower()
|
||||
print('hint_fn called, ans=', ans)
|
||||
hint = ''
|
||||
if 'java' in ans:
|
||||
hint = 'that is only good for drinking'
|
||||
elif 'perl' in ans:
|
||||
hint = 'not that rich'
|
||||
elif 'pascal' in ans:
|
||||
hint = 'that is a beatnick language'
|
||||
elif 'fortran' in ans:
|
||||
hint = 'those were the good days'
|
||||
elif 'clu' in ans:
|
||||
hint = 'you must be invariant'
|
||||
if hint:
|
||||
hint = "<font color='blue'>Hint: {0}</font>".format(hint)
|
||||
new_cmap.set_hint_and_mode(aid,hint,'always')
|
||||
</script>
|
||||
<label>What is the best programming language that exists today? You may enter your answer in upper or lower case, with or without quotes.</label>
|
||||
<textline correct_answer="python"/>
|
||||
<hintgroup hintfn="hint_fn"/>
|
||||
</customresponse>
|
||||
</text>
|
||||
</problem>
|
||||
|
||||
@@ -1,60 +1,21 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Problem with Adaptive Hint in Latex
|
||||
source_code: |
|
||||
\subsection{Problem With Adaptive Hint}
|
||||
display_name: Problem with Adaptive Hint in Latex
|
||||
source_code: |
|
||||
\subsection{Problem With Adaptive Hint}
|
||||
|
||||
% Adaptive hints are messages provided to students which depend on
|
||||
% student input. These hints are produced using a script embedded
|
||||
% within the problem (written in Python).
|
||||
%
|
||||
% Here is an example. This example uses LaTeX as a high-level
|
||||
% soure language for the problem. The problem can also be coded
|
||||
% directly in XML.
|
||||
% Adaptive hints are messages provided to students which depend on
|
||||
% student input. These hints are produced using a script embedded
|
||||
% within the problem (written in Python).
|
||||
%
|
||||
% Here is an example. This example uses LaTeX as a high-level
|
||||
% soure language for the problem. The problem can also be coded
|
||||
% directly in XML.
|
||||
|
||||
This problem demonstrates a question with hints, based on using the
|
||||
{\tt hintfn} method.
|
||||
This problem demonstrates a question with hints, based on using the
|
||||
{\tt hintfn} method.
|
||||
|
||||
\begin{edXscript}
|
||||
def test_str(expect, ans):
|
||||
print(expect, ans)
|
||||
ans = ans.strip("'")
|
||||
ans = ans.strip('"')
|
||||
return expect == ans.lower()
|
||||
|
||||
def hint_fn(answer_ids, student_answers, new_cmap, old_cmap):
|
||||
aid = answer_ids[0]
|
||||
ans = str(student_answers[aid]).lower()
|
||||
print('hint_fn called, ans=', ans)
|
||||
hint = ''
|
||||
if 'java' in ans:
|
||||
hint = 'that is only good for drinking'
|
||||
elif 'perl' in ans:
|
||||
hint = 'not that rich'
|
||||
elif 'pascal' in ans:
|
||||
hint = 'that is a beatnick language'
|
||||
elif 'fortran' in ans:
|
||||
hint = 'those were the good days'
|
||||
elif 'clu' in ans:
|
||||
hint = 'you must be invariant'
|
||||
if hint:
|
||||
hint = "<font color='blue'>Hint: {0}</font>".format(hint)
|
||||
new_cmap.set_hint_and_mode(aid,hint,'always')
|
||||
\end{edXscript}
|
||||
|
||||
What is the best programming language that exists today? You may
|
||||
enter your answer in upper or lower case, with or without quotes.
|
||||
|
||||
\edXabox{type="custom" cfn='test_str' expect='python' hintfn='hint_fn'}
|
||||
markdown: !!null
|
||||
data: |
|
||||
<problem>
|
||||
<text>
|
||||
<p><h4>Problem With Adaptive Hint</h4></p>
|
||||
<p>This problem demonstrates a question with hints, based on using the <tt class="tt">hintfn</tt> method.</p>
|
||||
|
||||
<customresponse cfn="test_str" expect="python">
|
||||
<script type="text/python" system_path="python_lib">
|
||||
\begin{edXscript}
|
||||
def test_str(expect, ans):
|
||||
print(expect, ans)
|
||||
ans = ans.strip("'")
|
||||
@@ -77,12 +38,51 @@ data: |
|
||||
elif 'clu' in ans:
|
||||
hint = 'you must be invariant'
|
||||
if hint:
|
||||
hint = "<font color='blue'>Hint: {0}</font>".format(hint)
|
||||
hint = "<font color='blue'>Hint: {0}</font>".format(hint)
|
||||
new_cmap.set_hint_and_mode(aid,hint,'always')
|
||||
</script>
|
||||
<p>What is the best programming language that exists today? You may enter your answer in upper or lower case, with or without quotes.</p>
|
||||
<textline correct_answer="python"/>
|
||||
<hintgroup hintfn="hint_fn"/>
|
||||
</customresponse>
|
||||
</text>
|
||||
</problem>
|
||||
\end{edXscript}
|
||||
|
||||
What is the best programming language that exists today? You may
|
||||
enter your answer in upper or lower case, with or without quotes.
|
||||
|
||||
\edXabox{type="custom" cfn='test_str' expect='python' hintfn='hint_fn'}
|
||||
markdown: !!null
|
||||
data: |
|
||||
<problem>
|
||||
<text>
|
||||
<p><h4>Problem With Adaptive Hint</h4></p>
|
||||
<p>This problem demonstrates a question with hints, based on using the <tt class="tt">hintfn</tt> method.</p>
|
||||
|
||||
<customresponse cfn="test_str" expect="python">
|
||||
<script type="text/python" system_path="python_lib">
|
||||
def test_str(expect, ans):
|
||||
print(expect, ans)
|
||||
ans = ans.strip("'")
|
||||
ans = ans.strip('"')
|
||||
return expect == ans.lower()
|
||||
|
||||
def hint_fn(answer_ids, student_answers, new_cmap, old_cmap):
|
||||
aid = answer_ids[0]
|
||||
ans = str(student_answers[aid]).lower()
|
||||
print('hint_fn called, ans=', ans)
|
||||
hint = ''
|
||||
if 'java' in ans:
|
||||
hint = 'that is only good for drinking'
|
||||
elif 'perl' in ans:
|
||||
hint = 'not that rich'
|
||||
elif 'pascal' in ans:
|
||||
hint = 'that is a beatnick language'
|
||||
elif 'fortran' in ans:
|
||||
hint = 'those were the good days'
|
||||
elif 'clu' in ans:
|
||||
hint = 'you must be invariant'
|
||||
if hint:
|
||||
hint = "<font color='blue'>Hint: {0}</font>".format(hint)
|
||||
new_cmap.set_hint_and_mode(aid,hint,'always')
|
||||
</script>
|
||||
<p>What is the best programming language that exists today? You may enter your answer in upper or lower case, with or without quotes.</p>
|
||||
<textline correct_answer="python"/>
|
||||
<hintgroup hintfn="hint_fn"/>
|
||||
</customresponse>
|
||||
</text>
|
||||
</problem>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Text Input
|
||||
markdown: |
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for text input problems. Edit this component to replace this template with your own assessment.
|
||||
display_name: Text Input
|
||||
markdown: |
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for text input problems. Edit this component to replace this template with your own assessment.
|
||||
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
|
||||
= the correct answer
|
||||
or= optional acceptable variant of the correct answer
|
||||
= the correct answer
|
||||
or= optional acceptable variant of the correct answer
|
||||
|
||||
data: |
|
||||
<problem>
|
||||
<stringresponse answer="the correct answer" type="ci">
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<additional_answer answer="optional acceptable variant of the correct answer"/>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
</problem>
|
||||
data: |
|
||||
<problem>
|
||||
<stringresponse answer="the correct answer" type="ci">
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<additional_answer answer="optional acceptable variant of the correct answer"/>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
</problem>
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
---
|
||||
metadata:
|
||||
display_name: Text Input with Hints and Feedback
|
||||
markdown: |
|
||||
display_name: Text Input with Hints and Feedback
|
||||
markdown: |
|
||||
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.
|
||||
You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.
|
||||
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
|
||||
|
||||
= the correct answer {{You can specify optional feedback like this, which appears after this answer is submitted.}}
|
||||
or= optional acceptable variant of the correct answer
|
||||
not= optional incorrect answer such as a frequent misconception {{You can specify optional feedback for none, a subset, or all of the answers.}}
|
||||
= the correct answer {{You can specify optional feedback like this, which appears after this answer is submitted.}}
|
||||
or= optional acceptable variant of the correct answer
|
||||
not= optional incorrect answer such as a frequent misconception {{You can specify optional feedback for none, a subset, or all of the answers.}}
|
||||
|
||||
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
|
||||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
|
||||
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
|
||||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
|
||||
|
||||
hinted: true
|
||||
data: |
|
||||
<problem>
|
||||
<stringresponse answer="the correct answer" type="ci">
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<correcthint>You can specify optional feedback like this, which appears after this answer is submitted.</correcthint>
|
||||
<additional_answer answer="optional acceptable variant of the correct answer"/>
|
||||
<stringequalhint answer="optional incorrect answer such as a frequent misconception">You can specify optional feedback for none, a subset, or all of the answers.</stringequalhint>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<demandhint>
|
||||
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
|
||||
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
data: |
|
||||
<problem>
|
||||
<stringresponse answer="the correct answer" type="ci">
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this.</description>
|
||||
<correcthint>You can specify optional feedback like this, which appears after this answer is submitted.</correcthint>
|
||||
<additional_answer answer="optional acceptable variant of the correct answer"/>
|
||||
<stringequalhint answer="optional incorrect answer such as a frequent misconception">You can specify optional feedback for none, a subset, or all of the answers.</stringequalhint>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<demandhint>
|
||||
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
|
||||
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
|
||||
Reference in New Issue
Block a user