diff --git a/xblocks_contrib/problem/assets/spec/collapsible_spec.js b/xblocks_contrib/problem/assets/spec/collapsible_spec.js
index a924e1cb..7aed677d 100644
--- a/xblocks_contrib/problem/assets/spec/collapsible_spec.js
+++ b/xblocks_contrib/problem/assets/spec/collapsible_spec.js
@@ -1,130 +1,119 @@
-// eslint-disable-next-line no-shadow-restricted-names
-(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 () {
+ let $el;
+ const initialize = function (template) {
+ setFixtures(template);
+ $el = $(".collapsible");
+ Collapsible.setCollapsibles($el);
+ };
+ const disableFx = function () {
+ $.fx.off = true;
+ };
+ const enableFx = function () {
+ $.fx.off = false;
+ };
+
+ let html, html_custom;
+ beforeEach(function () {
+ html =
+ "" +
+ '' +
+ 'shortform message
' +
+ '" +
+ "";
+ html_custom =
+ "" +
+ '' +
+ "shortform message
" +
+ '" +
+ "";
+ });
- beforeEach(function () {
- html =
- "" +
- '' +
- 'shortform message
' +
- '" +
- "";
- html_custom =
- "" +
- '' +
- "shortform message
" +
- '" +
- "";
+ 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");
});
- describe("setCollapsibles", function () {
- it("Default container initialized correctly", function () {
- initialize(html);
+ it("Custom container initialized correctly", function () {
+ initialize(html_custom);
- 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");
- });
+ 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 () {
+ const assertChanges = function (state, anchorsElClass, showText, hideText) {
+ if (state == null) {
+ state = "closed";
+ }
- it("Custom container initialized correctly", function () {
- initialize(html_custom);
+ const anchors = $el.find("." + anchorsElClass);
- expect($el.find(".shortform-custom")).toContainElement(".full-custom");
- expect($el.find(".full-custom")).toHaveText("Show shortform-custom");
+ let text;
+ if (state === "closed") {
expect($el.find(".longform")).toBeHidden();
- expect($el.find(".full-custom")).toHandle("click");
+ 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();
});
- 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();
+ });
- afterEach(function () {
- enableFx();
- });
+ it("Default container", function () {
+ initialize(html);
- it("Default container", function () {
- var event;
+ const event = jQuery.Event("click", {
+ target: $el.find(".full").get(0),
+ });
- initialize(html);
+ Collapsible.toggleFull(event, "See full output", "Hide output");
+ assertChanges("opened", "full", "See full output", "Hide output");
- event = jQuery.Event("click", {
- target: $el.find(".full").get(0),
- });
+ Collapsible.toggleFull(event, "See full output", "Hide output");
+ assertChanges("closed", "full", "See full output", "Hide output");
+ });
- Collapsible.toggleFull(event, "See full output", "Hide output");
- assertChanges("opened", "full", "See full output", "Hide output");
+ it("Custom container", function () {
+ initialize(html_custom);
- Collapsible.toggleFull(event, "See full output", "Hide output");
- assertChanges("closed", "full", "See full output", "Hide output");
+ const event = jQuery.Event("click", {
+ target: $el.find(".full-custom").get(0),
});
- it("Custom container", function () {
- var event;
+ Collapsible.toggleFull(event, "Show shortform-custom", "Hide shortform-custom");
+ assertChanges("opened", "full-custom", "Show shortform-custom", "Hide shortform-custom");
- 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");
- });
+ Collapsible.toggleFull(event, "Show shortform-custom", "Hide shortform-custom");
+ assertChanges("closed", "full-custom", "Show shortform-custom", "Hide shortform-custom");
});
});
-}).call(this);
+});
diff --git a/xblocks_contrib/problem/assets/spec/display_spec.js b/xblocks_contrib/problem/assets/spec/display_spec.js
index 843ea6a9..d7ee80f1 100644
--- a/xblocks_contrib/problem/assets/spec/display_spec.js
+++ b/xblocks_contrib/problem/assets/spec/display_spec.js
@@ -1,12 +1,6 @@
-/*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * 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");
- var mockRuntime = {};
+ const mockRuntime = {};
beforeEach(function () {
// Stub MathJax
@@ -37,7 +31,7 @@ describe("Problem", function () {
describe("constructor", function () {
it("set the element from html", function () {
- this.problem999 = new Problem(mockRuntime,`\
+ this.problem999 = new Problem(mockRuntime, `\
\
\
describe("refreshMath", function () {
beforeEach(function () {
this.problem = new Problem(mockRuntime, $(".xblock-student_view"));
+ // Reset Queue spy so that bind()'s Queue call ([fn, null, domEl]) is not
+ // included when toHaveBeenCalledWith scans recorded calls. In Jasmine 2.99,
+ // toHaveBeenCalledWith iterates ALL recorded calls' args element-by-element
+ // (even mismatched ones, for diff output). jasmine-jquery's custom equality
+ // tester calls $(domEl).is(anyString) when comparing a DOM node against a
+ // string — which throws a Sizzle syntax error if the string isn't a valid
+ // CSS selector (e.g. "E=mc^2"). Resetting here isolates this describe to
+ // testing only what refreshMath itself queues.
+ MathJax.Hub.Queue.calls.reset();
$("#input_example_1").val("E=mc^2");
this.problem.refreshMath({ target: $("#input_example_1").get(0) });
});
diff --git a/xblocks_contrib/problem/assets/spec/imageinput_spec.js b/xblocks_contrib/problem/assets/spec/imageinput_spec.js
index f6c2f9f9..eb6c5452 100644
--- a/xblocks_contrib/problem/assets/spec/imageinput_spec.js
+++ b/xblocks_contrib/problem/assets/spec/imageinput_spec.js
@@ -5,128 +5,90 @@
* ~ Donald Knuth
*/
-// eslint-disable-next-line no-shadow-restricted-names
-(function ($, ImageInput, undefined) {
- describe("ImageInput", function () {
- var state;
-
- beforeEach(function () {
- var $el;
+describe('ImageInput', function() {
+ let state;
+
+ beforeEach(function() {
+ loadFixtures('imageinput.html');
+ const $el = $('#imageinput_12345');
+ $el.append(createTestImage('cross_12345', 300, 400, 'red'));
+ state = new ImageInput('12345');
+ });
- loadFixtures("imageinput.html");
- $el = $("#imageinput_12345");
+ it('initialization', function() {
+ expect(state.el).toBeDefined();
+ expect(state.el).toExist();
- $el.append(createTestImage("cross_12345", 300, 400, "red"));
+ expect(state.crossEl).toBeDefined();
+ expect(state.crossEl).toExist();
- state = new ImageInput("12345");
- });
+ expect(state.inputEl).toBeDefined();
+ expect(state.inputEl).toExist();
- 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.el).toHandle('click');
+ });
- expect(state.crossEl).toBeDefined();
- expect(state.crossEl).toExist();
+ 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');
+ });
- expect(state.inputEl).toBeDefined();
- expect(state.inputEl).toExist();
+ it('coordinates are updated [offsetX is set]', function() {
+ const event = jQuery.Event('click', { offsetX: 35.3, offsetY: 42.7 });
+ const posX = event.offsetX;
+ const posY = event.offsetY;
- expect(state.el).toHandle("click");
- });
+ jQuery(state.el).trigger(event);
- it("cross becomes visible after first click", function () {
- expect(state.crossEl.css("visibility")).toBe("hidden");
+ const cssLeft = stripPx(state.crossEl.css('left'));
+ const cssTop = stripPx(state.crossEl.css('top'));
- state.el.click();
+ expect(cssLeft).toBeCloseTo(posX - 15, 1);
+ expect(cssTop).toBeCloseTo(posY - 15, 1);
+ expect(state.inputEl.val()).toBe(`[${Math.round(posX)},${Math.round(posY)}]`);
+ });
- expect(state.crossEl.css("visibility")).toBe("visible");
+ it('coordinates are updated [offsetX is NOT set]', function() {
+ const offset = state.el.offset();
+ const event = jQuery.Event('click', {
+ offsetX: undefined,
+ offsetY: undefined,
+ pageX: 35.3,
+ pageY: 42.7,
});
+ const posX = event.pageX - offset.left;
+ const posY = event.pageY - offset.top;
- it("coordinates are updated [offsetX is set]", function () {
- var event, posX, posY, cssLeft, cssTop;
+ jQuery(state.el).trigger(event);
- // Set up of 'click' event.
- event = jQuery.Event("click", { offsetX: 35.3, offsetY: 42.7 });
+ const cssLeft = stripPx(state.crossEl.css('left'));
+ const cssTop = stripPx(state.crossEl.css('top'));
- // 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) + "]");
- });
+ 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
tag, we will generate one on the fly.
- //
- // Create a simple image from a canvas. The canvas is filled by a colored
- // rectangle.
+ // Generate a simple canvas-based image on the fly rather than loading a fixture file.
function createTestImage(id, width, height, fillStyle) {
- var canvas, ctx, img;
-
- canvas = document.createElement("canvas");
+ const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
- ctx = canvas.getContext("2d");
+ const ctx = canvas.getContext('2d');
ctx.fillStyle = fillStyle;
ctx.fillRect(0, 0, width, height);
- img = document.createElement("img");
- img.src = canvas.toDataURL("image/png");
+ const 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.
+ // Strip the trailing 'px' from a CSS left/top value string.
function stripPx(str) {
return str.substring(0, str.length - 2);
}
-}).call(this, window.jQuery, window.ImageInput);
+});
diff --git a/xblocks_contrib/problem/assets/static/js/collapsible.js b/xblocks_contrib/problem/assets/static/js/collapsible.js
index 46b240d6..0bbf909e 100644
--- a/xblocks_contrib/problem/assets/static/js/collapsible.js
+++ b/xblocks_contrib/problem/assets/static/js/collapsible.js
@@ -1,121 +1,106 @@
-// eslint-disable-next-line no-shadow-restricted-names
-(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,
- };
-
- // 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;
-
- linkTop = 'See full output';
- linkBottom = 'See full output';
-
- // 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");
-
- // 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(""),
- gettext(open_text),
- edx.HtmlUtils.HTML(""),
- ),
- );
-
- $(elt)
- .find(".full-custom")
- .click(function (event) {
- Collapsible.toggleFull(event, open_text, close_text);
- });
- });
-
- // 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);
- }
-
- // [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);
+// [module Collapsible]
+//
+// [description]
+// Set of library functions that provide a simple way to add
+// collapsible functionality to elements.
+const Collapsible = {
+ setCollapsibles,
+ toggleFull,
+ toggleHint,
+};
+
+// [function setCollapsibles]
+//
+// [description]
+// Scan element's content for generic collapsible containers.
+//
+// [params]
+// el: container
+function setCollapsibles(el) {
+ const linkTop = 'See full output';
+ const linkBottom = 'See full output';
+
+ // 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.
+ const short_custom = el.find(".shortform-custom");
+
+ // Set up each one individually.
+ short_custom.each(function (index, elt) {
+ const open_text = $(elt).data("open-text");
+ const close_text = $(elt).data("close-text");
+ edx.HtmlUtils.append(
+ $(elt),
+ edx.HtmlUtils.joinHtml(
+ edx.HtmlUtils.HTML(""),
+ gettext(open_text),
+ edx.HtmlUtils.HTML(""),
+ ),
+ );
+
+ $(elt)
+ .find(".full-custom")
+ .click(function (event) {
+ Collapsible.toggleFull(event, open_text, close_text);
+ });
+ });
+
+ // 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);
+}
+
+// [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) {
+ event.preventDefault();
+
+ const parent = $(event.target).parent();
+ parent.siblings().slideToggle();
+ parent.parent().toggleClass("open");
+
+ const new_text = $(event.target).text() === open_text ? close_text : open_text;
+
+ let $el;
+ 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();
-
- $(event.target).parent().siblings().slideToggle();
- $(event.target).parent().parent().toggleClass("open");
- }
-}).call(this);
+ $el.text(new_text);
+}
+
+// [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");
+}
+
+window.Collapsible = Collapsible;
diff --git a/xblocks_contrib/problem/assets/static/js/display.js b/xblocks_contrib/problem/assets/static/js/display.js
index 96fd56b2..e2d50837 100644
--- a/xblocks_contrib/problem/assets/static/js/display.js
+++ b/xblocks_contrib/problem/assets/static/js/display.js
@@ -5,1407 +5,1264 @@
// the max line length of 120.
/* eslint max-len: ["error", 120, { "ignoreComments": true }] */
-(function () {
- "use strict";
+function Problem(runtime, element) { // eslint-disable-line no-unused-vars
+ this.hint_button = this.hint_button.bind(this);
+ this.enableSubmitButtonAfterTimeout = this.enableSubmitButtonAfterTimeout.bind(this);
+ this.enableSubmitButtonAfterResponse = this.enableSubmitButtonAfterResponse.bind(this);
+ this.enableSubmitButton = this.enableSubmitButton.bind(this);
+ this.disableAllButtonsWhileRunning = this.disableAllButtonsWhileRunning.bind(this);
+ this.submitAnswersAndSubmitButton = this.submitAnswersAndSubmitButton.bind(this);
+ this.refreshAnswers = this.refreshAnswers.bind(this);
+ this.updateMathML = this.updateMathML.bind(this);
+ this.refreshMath = this.refreshMath.bind(this);
+ this.save_internal = this.save_internal.bind(this);
+ this.save = this.save.bind(this);
+ this.gentle_alert = this.gentle_alert.bind(this);
+ this.clear_all_notifications = this.clear_all_notifications.bind(this);
+ this.show = this.show.bind(this);
+ this.reset_internal = this.reset_internal.bind(this);
+ this.reset = this.reset.bind(this);
+ this.get_sr_status = this.get_sr_status.bind(this);
+ this.submit_internal = this.submit_internal.bind(this);
+ this.submit = this.submit.bind(this);
+ this.submit_fd = this.submit_fd.bind(this);
+ this.focus_on_save_notification = this.focus_on_save_notification.bind(this);
+ this.focus_on_hint_notification = this.focus_on_hint_notification.bind(this);
+ this.focus_on_submit_notification = this.focus_on_submit_notification.bind(this);
+ this.focus_on_notification = this.focus_on_notification.bind(this);
+ this.scroll_to_problem_meta = this.scroll_to_problem_meta.bind(this);
+ this.submit_save_waitfor = this.submit_save_waitfor.bind(this);
+ this.setupInputTypes = this.setupInputTypes.bind(this);
+ this.poll = this.poll.bind(this);
+ this.queueing = this.queueing.bind(this);
+ this.forceUpdate = this.forceUpdate.bind(this);
+ this.updateProgress = this.updateProgress.bind(this);
+ this.renderProgressState = this.renderProgressState.bind(this);
+ this.bind = this.bind.bind(this);
+ 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");
- 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;
- };
+ // 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);
+}
- this.Problem = function () {
- function Problem(runtime, 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.$ = function (selector) {
+ return $(selector, this.el);
+};
- // 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);
- }
+Problem.prototype.bind = function () {
+ const that = this;
+ let problemPrefix;
+ 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");
- Problem.prototype.$ = function (selector) {
- return $(selector, this.el);
- };
+ // Accessibility helper for sighted keyboard users to show tooltips on focus:
+ this.$(".clarification").focus(function (ev) {
+ const 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.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 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.renderProgressState = function () {
+ let graded, progressTemplate;
+ const curScore = this.el.data("problem-score");
+ const totalScore = this.el.data("problem-total-possible");
+ const attemptsUsed = this.el.data("attempts-used");
+ graded = this.el.data("graded");
- 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();
- };
+ // 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;
+ }
- Problem.prototype.convertToFloat = function (num) {
- if (typeof num !== "number" || !Number.isInteger(num)) {
- return num;
- }
- return num.toFixed(1);
- };
+ 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,
+ );
+ }
+ }
+ const progress = edx.StringUtils.interpolate(progressTemplate, {
+ earned: curScore,
+ num_points: totalScore,
+ possible: totalScore,
+ });
+ return this.$(".problem-progress").text(progress);
+};
- 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.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.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.convertToFloat = function (num) {
+ if (typeof num !== "number" || !Number.isInteger(num)) {
+ return num;
+ }
+ return num.toFixed(1);
+};
- 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);
- }
- }
- });
- };
+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();
+};
- /**
- * 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.queueing = function (focusCallback) {
+ const 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.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();
+Problem.prototype.poll = function (previousTimeout, focusCallback) {
+ const that = this;
+ return $.postWithPrefix(`${this.url}/problem_get`, function (response) {
+ let 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 {
- 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);
- });
- });
+ window.queuePollerID = window.setTimeout(function () {
+ return that.poll(newTimeout, focusCallback);
+ }, newTimeout);
}
- };
+ }
+ });
+};
- 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;
+/**
+ * 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) {
+ const 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);
});
- };
+ });
+ }
+};
- /**
- * 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;
+Problem.prototype.setupInputTypes = function () {
+ const that = this;
+ this.inputtypeDisplays = {};
+ return this.el.find(".capa_inputtype").each(function (index, inputtype) {
+ let cls, setupMethod;
+ const classes = $(inputtype).attr("class").split(" ");
+ const id = $(inputtype).attr("id");
+ const results = [];
+ for (let 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) {
+ const that = this;
+ let flag = false;
+ const ref = this.inputs;
+ for (let i = 0, len = ref.length; i < len; i++) {
+ const 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 {
- flag = false;
+ alert(
+ // eslint-disable-line no-alert
+ gettext("Could not grade your answer. The submission was aborted."),
+ );
}
+ throw e;
}
- return flag;
- };
+ 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();
- }
- };
+// Scroll to problem metadata and next focus is problem input
+Problem.prototype.scroll_to_problem_meta = function () {
+ const 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_notification = function (type) {
+ const 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_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_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");
- };
+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;
+/**
+ * '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 () {
+ const that = this;
+ let abortSubmission,
+ error,
+ errorHtml = "",
+ fileTooLarge,
+ fileNotSelected,
+ requiredFilesNotSubmitted,
+ unallowedFileSubmitted;
- // 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();
+ // 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;
+ }
+ const timeoutId = this.enableSubmitButtonAfterTimeout();
+ const fd = new FormData();
- // Sanity checks on submission
- maxFileSize = 4 * 1000 * 1000;
- fileTooLarge = false;
- fileNotSelected = false;
- requiredFilesNotSubmitted = false;
- unallowedFileSubmitted = false;
+ // Sanity checks on submission
+ const 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
+ const errors = [];
+ this.inputs.each(function (index, element) {
+ let file, maxSize;
+ if (element.type === "file") {
+ const requiredFiles = $(element).data("required_files");
+ const allowedFiles = $(element).data("allowed_files");
+ const ref = element.files;
+ for (let loopI = 0, loopLen = ref.length; loopI < loopLen; loopI++) {
+ file = ref[loopI];
+ if (allowedFiles.length !== 0 && !allowedFiles.includes(file.name)) {
+ unallowedFileSubmitted = true;
+ errors.push(
+ edx.StringUtils.interpolate(gettext("You submitted {filename}; only {allowedFiles} are allowed."), {
+ filename: file.name,
+ allowedFiles: allowedFiles,
+ }),
+ );
}
- });
- 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("{error}"), { error: error }),
- );
+ if (requiredFiles.includes(file.name)) {
+ 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
}
- errorHtml = edx.HtmlUtils.interpolateHtml(edx.HtmlUtils.HTML(""), { 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);
+ 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
}
- };
-
- Problem.prototype.submit = function () {
- if (!this.submit_save_waitfor(this.submit_internal)) {
- this.disableAllButtonsWhileRunning(this.submit_internal, true);
+ if (requiredFiles.length !== 0) {
+ requiredFilesNotSubmitted = true;
+ errors.push(
+ edx.StringUtils.interpolate(gettext("You did not submit the required files: {requiredFiles}."), {
+ requiredFiles: requiredFiles,
+ }),
+ );
}
- };
-
- Problem.prototype.submit_internal = function () {
- var that = this;
- Logger.log("problem_check", this.answers);
- return $.postWithPrefix("" + this.url + "/problem_check", this.answers, function (response) {
+ } 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."));
+ }
+ for (let i = 0, len = errors.length; i < len; i++) {
+ error = errors[i];
+ errorHtml = edx.HtmlUtils.joinHtml(
+ errorHtml,
+ edx.HtmlUtils.interpolateHtml(edx.HtmlUtils.HTML("{error}"), { error: error }),
+ );
+ }
+ errorHtml = edx.HtmlUtils.interpolateHtml(edx.HtmlUtils.HTML(""), { errors: errorHtml });
+ this.gentle_alert(errorHtml.toString());
+ abortSubmission = fileTooLarge || fileNotSelected || unallowedFileSubmitted || requiredFilesNotSubmitted;
+ if (abortSubmission) {
+ window.clearTimeout(timeoutId);
+ this.enableSubmitButton(true);
+ } else {
+ const settings = {
+ type: "POST",
+ data: fd,
+ processData: false,
+ contentType: false,
+ complete: this.enableSubmitButtonAfterResponse,
+ success: 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.render(response.contents);
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);
- });
+ },
+ error: function (response) {
+ that.gentle_alert(response.responseJSON.success);
+ },
};
+ $.ajaxWithPrefix(`${this.url}/problem_check`, settings);
+ }
+};
- /**
- * 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());
+Problem.prototype.submit = function () {
+ if (!this.submit_save_waitfor(this.submit_internal)) {
+ this.disableAllButtonsWhileRunning(this.submit_internal, true);
+ }
+};
+
+Problem.prototype.submit_internal = function () {
+ const 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) {
+ let addedStatus, ariaLabel, element, parentSection;
+ const statusElement = $(contents).find(".status");
+ const labeledStatus = [];
+ for (let 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.;
+ const template = gettext("{label}: {status}");
+ labeledStatus.push(
+ edx.StringUtils.interpolate(template, {
+ label: ariaLabel,
+ status: $(element).text(),
+ }),
+ );
+ addedStatus = true;
}
- return labeledStatus;
- };
+ }
+ if (!addedStatus) {
+ labeledStatus.push($(element).text());
+ }
+ }
+ return labeledStatus;
+};
- Problem.prototype.reset = function () {
- return this.disableAllButtonsWhileRunning(this.reset_internal, false);
- };
+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);
- }
- },
- );
- };
+Problem.prototype.reset_internal = function () {
+ const 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(/\\/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);
+// 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 () {
+ const that = this;
+ Logger.log("problem_show", {
+ problem: this.id,
+ });
+ return $.postWithPrefix(`${this.url}/problem_show`, function (response) {
+ const answers = response.answers;
+ $.each(answers, function (key, value) {
+ const safeKey = key.replace(/\\/g, "\\\\").replace(/:/g, "\\:").replace(/\./g, "\\."); // fix for courses which use url_names with colons & periods, e.g. problem:question1, question1.1
+ let 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 {};
- }
+ // 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]);
- });
+ // TODO remove the above once everything is extracted into its own
+ // inputtype functions.
+ }
+ });
+ that.el.find(".capa_inputtype").each(function (index, inputtype) {
+ let cls, showMethod;
+ const classes = $(inputtype).attr("class").split(" ");
+ const results = [];
+ for (let i = 0, len = classes.length; i < len; i++) {
+ cls = classes[i];
+ const 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);
}
- that.el.find(".show").attr("disabled", "disabled");
- that.updateProgress(response);
- that.clear_all_notifications();
- that.showAnswerNotification.show();
- that.focus_on_notification("show-answer");
+ }
+ 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.clear_all_notifications = function () {
- this.submitNotification.remove();
- this.gentleAlertNotification.hide();
- this.saveNotification.hide();
- this.showAnswerNotification.hide();
- };
+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.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 = 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.save_internal = function () {
+ const that = this;
+ Logger.log("problem_save", this.answers);
+ return $.postWithPrefix(`${this.url}/problem_save`, this.answers, function (response) {
+ const 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;
+Problem.prototype.refreshMath = function (event, element) {
+ let 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]);
- }
- };
+ // 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.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();
- };
+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
+/**
+ * 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) {
+ const that = this;
+ let answered, atLeastOneTextInputFound, oneTextInputFilled;
+ 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;
}
- 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;
+ if (bind) {
+ $(textField).on("input", function () {
+ that.saveNotification.hide();
+ that.showAnswerNotification.hide();
+ that.submitAnswersAndSubmitButton();
+ });
}
- 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 (atLeastOneTextInputFound && !oneTextInputFilled) {
+ answered = false;
+ }
+ this.el.find(".choicegroup").each(function (i, choicegroupBlock) {
+ let checked = false;
+ $(choicegroupBlock)
+ .find("input[type=checkbox], input[type=radio]")
+ .each(function (j, checkboxOrRadio) {
+ if ($(checkboxOrRadio).is(":checked")) {
+ checked = true;
}
if (bind) {
- $(selectField).on("change", function () {
+ $(checkboxOrRadio).on("click", function () {
that.saveNotification.hide();
+ that.el.find(".show").removeAttr("disabled");
that.showAnswerNotification.hide();
that.submitAnswersAndSubmitButton();
});
}
});
- if (answered) {
- return this.enableSubmitButton(true);
+ if (!checked) {
+ answered = false;
+ }
+ });
+ this.el.find("select").each(function (i, selectField) {
+ const 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.bindResetCorrectness = function () {
+ // Loop through all input types.
+ // Bind the reset functions at that scope.
+ const that = this;
+ const $inputtypes = this.el.find(".capa_inputtype").add(this.el.find(".inputtype"));
+ return $inputtypes.each(function (index, inputtype) {
+ let bindMethod, cls;
+ const classes = $(inputtype).attr("class").split(" ");
+ const results = [];
+ for (let i = 0, len = classes.length; i < len; i++) {
+ cls = classes[i];
+ bindMethod = that.bindResetCorrectnessByInputtype[cls];
+ if (bindMethod != null) {
+ results.push(bindMethod(inputtype));
} else {
- return this.enableSubmitButton(false, false);
+ // eslint-disable-next-line no-void
+ results.push(void 0);
}
- };
+ }
+ return results;
+ });
+};
- 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 to reset the whole.
+ formulaequationinput: function (element) {
+ return $(element)
+ .find("input")
+ .on("input", function () {
+ const $p = $(element).find("span.status");
+ $p.removeClass("correct incorrect submitted");
+ return $p.parent().removeAttr("class").addClass("unsubmitted");
});
- };
-
- // 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 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 {
- $("", {
- 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"));
+ },
+ choicegroup: function (element) {
+ const $element = $(element);
+ const id = $element.attr("id").match(/^inputtype_(.*)$/)[1];
+ return $element.find("input").on("change", function () {
+ const $status = $(`#status_${id}`);
+ if ($status[0]) {
+ $status.removeAttr("class").addClass("status unanswered");
+ } else {
+ $("", {
+ class: "status unanswered",
+ style: "display: inline-block;",
+ id: `status_${id}`,
});
- },
- 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");
- });
- },
- };
+ }
+ $element.find("label").find("span.status.correct").remove();
+ return $element.find("label").removeAttr("class");
+ });
+ },
+ "option-input": function (element) {
+ const $select = $(element).find("select");
+ const 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 () {
+ const $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) {
+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
+ */
+ const data = $(element).find(".text-input-dynamath_data");
+ const preprocessorClassName = data.data("preprocessor");
+ const preprocessorClass = window[preprocessorClassName];
+ if (preprocessorClass == null) {
+ return false;
+ } else {
+ const preprocessor = new preprocessorClass();
+ return preprocessor.fn;
+ }
+ },
+ cminput: function (container) {
+ const element = $(container).find("textarea");
+ const tabsize = element.data("tabsize");
+ const mode = element.data("mode");
+ const linenumbers = element.data("linenums");
+ const spaces = Array(parseInt(tabsize, 10) + 1).join(" ");
+ const 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;
- } 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;
- },
- 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;
+ },
+ Tab: function (cm) {
+ cm.replaceSelection(spaces, "end");
+ return false;
+ },
},
- };
+ });
+ const id = element.attr("id").replace(/^input_/, "");
+ const 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_/, "");
- 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.
+Problem.prototype.inputtypeShowAnswerMethods = {
+ choicegroup: function (element, display, answers, correctStatusHtml) {
+ let choice;
+ const $element = $(element);
+ const inputId = $element.attr("id").replace(/inputtype_/, "");
+ const safeId = inputId.replace(/\\/g, "\\\\").replace(/:/g, "\\:").replace(/\./g, "\\."); // fix for courses which use url_names with colons & periods, e.g. problem:question1, question1.1
+ const answer = answers[inputId];
+ const results = [];
+ for (let i = 0, len = answer.length; i < len; i++) {
+ choice = answer[i];
+ const $inputLabel = $element.find(`#input_${safeId}_${choice} + label`);
+ const $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"));
+ // 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) {
+ let choice;
+ const $element = $(element);
+ const inputId = $element.attr("id").replace(/inputtype_/, "");
+ const answer = answers[inputId];
+ const results = [];
+ for (let 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]]'
+ // } }
+ const types = {
+ rectangle: function (ctx, coords) {
+ const reg = /^\(([0-9]+),([0-9]+)\)-\(([0-9]+),([0-9]+)\)$/;
+ const rects = coords.replace(/\s*/g, "").split(/;/);
+ $.each(rects, function (index, rect) {
+ const abs = Math.abs;
+ const points = reg.exec(rect);
+ if (points) {
+ const width = abs(points[3] - points[1]);
+ const height = abs(points[4] - points[2]);
+ ctx.rect(points[1], points[2], width, height);
}
- }
- 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;
+ });
+ ctx.stroke();
+ return ctx.fill();
},
- 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);
+ regions: function (ctx, coords) {
+ const parseCoords = function (coordinates) {
+ let reg;
+ reg = JSON.parse(coordinates);
- // 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();
- });
- },
+ // 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;
};
- $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);
+ 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]);
}
});
- 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
- }
+ ctx.closePath();
+ ctx.stroke();
+ return ctx.fill();
+ });
},
};
-
- /**
- * 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" });
+ const $element = $(element);
+ const id = $element.attr("id").replace(/inputtype_/, "");
+ const container = $element.find(`#answer_${id}`);
+ const canvas = document.createElement("canvas");
+ canvas.width = container.data("width");
+ canvas.height = container.data("height");
+ let context;
+ 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
+ }
+ },
+};
- /**
- * 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);
- }
- }
- };
+/**
+ * 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) {
+ const that = this;
+ const allButtons = [this.resetButton, this.saveButton, this.showButton, this.hintButton, this.submitButton];
+ const initiallyEnabledButtons = allButtons.filter(function (button) {
+ return !button.attr("disabled");
+ });
+ this.enableButtons(initiallyEnabledButtons, false, isFromCheckOperation);
+ return operationCallback().always(function () {
+ return that.enableButtons(initiallyEnabledButtons, true, isFromCheckOperation);
+ });
+};
- 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);
- }
- };
+/**
+ * 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) {
+ const 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" });
+ }
+ });
+};
- 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);
- };
+/**
+ * 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) {
+ if (changeText === null || changeText === undefined) {
+ changeText = true; // eslint-disable-line no-param-reassign
+ }
+ if (enable) {
+ const 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.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;
+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 () {
+ const that = this;
+ this.has_timed_out = false;
+ this.has_response = false;
+ const 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.
+ const that = this;
+ const hintContainer = this.$(".problem-hint");
+ const hintIndex = hintContainer.attr("hint_index");
+ let nextIndex;
+ // 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) {
+ if (response.success) {
+ const 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 {
- nextIndex = parseInt(hintIndex, 10) + 1;
+ that.gentle_alert(response.msg);
}
- 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);
+window.Problem = Problem;
diff --git a/xblocks_contrib/problem/assets/static/js/imageinput.js b/xblocks_contrib/problem/assets/static/js/imageinput.js
index 40913661..1eab27ae 100644
--- a/xblocks_contrib/problem/assets/static/js/imageinput.js
+++ b/xblocks_contrib/problem/assets/static/js/imageinput.js
@@ -1,54 +1,33 @@
/**
* Simple image input
*
- *
* Click on image. Update the coordinates of a dot on the image.
* The new coordinates are the location of the click.
*/
-
-/**
- * 'The wise adapt themselves to circumstances, as water molds itself to the
- * pitcher.'
- *
- * ~ Chinese Proverb
- */
-
-// eslint-disable-next-line no-shadow-restricted-names
-window.ImageInput = function ($, undefined) {
- var ImageInput = ImageInputConstructor;
-
- ImageInput.prototype = {
- constructor: ImageInputConstructor,
- clickHandler: clickHandler,
- };
-
- return ImageInput;
-
- function ImageInputConstructor(elementId) {
- this.el = $("#imageinput_" + elementId);
- this.crossEl = $("#cross_" + elementId);
- this.inputEl = $("#input_" + elementId);
-
- this.el.on("click", this.clickHandler.bind(this));
+class ImageInput {
+ constructor(elementId) {
+ this.el = $(`#imageinput_${elementId}`);
+ this.crossEl = $(`#cross_${elementId}`);
+ this.inputEl = $(`#input_${elementId}`);
+ 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,
- // 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) + "]";
+ clickHandler(event) {
+ const offset = this.el.offset();
+ // offsetX/Y is unavailable in some older browsers; fall back to pageX/Y minus element offset.
+ const posX = event.offsetX !== undefined ? event.offsetX : event.pageX - offset.left;
+ const posY = event.offsetY !== undefined ? event.offsetY : event.pageY - offset.top;
+ // Round to reduce float differences across browsers (IE10 returns floats, Chrome/FF integers).
+ const result = `[${Math.round(posX)},${Math.round(posY)}]`;
this.crossEl.css({
left: posX - 15,
top: posY - 15,
- visibility: "visible",
+ visibility: 'visible',
});
this.inputEl.val(result);
}
-}.call(this, window.jQuery);
+}
+
+window.ImageInput = ImageInput;
diff --git a/xblocks_contrib/problem/assets/static/js/javascript_loader.js b/xblocks_contrib/problem/assets/static/js/javascript_loader.js
index 71fb4382..1d62994d 100644
--- a/xblocks_contrib/problem/assets/static/js/javascript_loader.js
+++ b/xblocks_contrib/problem/assets/static/js/javascript_loader.js
@@ -1,97 +1,86 @@
-(function () {
- "use strict";
+/**
+ * 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 of document
+ * 3) Explicitly wait for each script to be loaded
+ * 4) Return to callback function when all scripts loaded
+ */
+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 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
+JavascriptLoader.executeModuleScripts = function (el, callback) {
+ if (!callback) {
+ callback = null; // eslint-disable-line no-param-reassign
+ }
+ const placeholders = el.find(".script_placeholder");
+ if (placeholders.length === 0) {
+ if (callback !== null) {
+ callback();
+ }
+ return [];
+ }
+ // TODO: Verify the execution order of multiple placeholders
+ const completed = (function () {
+ const results = [];
+ for (let i = 1, ref = placeholders.length; ref >= 1 ? i <= ref : i >= ref; ref >= 1 ? ++i : --i) {
+ results.push(false);
+ }
+ return results;
+ })();
+ let callbackCalled = false;
+ const completionHandlerGenerator = function (index) {
+ return function () {
+ let allComplete = true;
+ completed[index] = true;
+ for (let i = 0, len = completed.length; i < len; i++) {
+ if (!completed[i]) {
+ allComplete = false;
+ break;
+ }
}
- placeholders = el.find(".script_placeholder");
- if (placeholders.length === 0) {
+ if (allComplete && !callbackCalled) {
+ callbackCalled = true;
if (callback !== null) {
- callback();
+ return callback();
}
- 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;
- }
- }
- 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, 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, "%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 undefined;
};
+ };
+ // Keep a map of what sources we're loaded from, and don't do it twice.
+ const loaded = {};
+ return placeholders.each(function (index, placeholder) {
+ // 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.
+ const 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();
+ }
+ const src_escaped = String(src || "")
+ .replace(//g, "%3E");
+ if (!(src_escaped in loaded)) {
+ loaded[src_escaped] = true;
+ const 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;
- })();
-}).call(this);
+window.JavascriptLoader = JavascriptLoader;
diff --git a/xblocks_contrib/problem/assets/static/js/schematic.js b/xblocks_contrib/problem/assets/static/js/schematic.js
index 7004562a..f10822fb 100644
--- a/xblocks_contrib/problem/assets/static/js/schematic.js
+++ b/xblocks_contrib/problem/assets/static/js/schematic.js
@@ -1,5 +1,6 @@
/* eslint-disable */
+
//////////////////////////////////////////////////////////////////////////////
//
// Circuit simulator
@@ -13,7 +14,7 @@
// for modified nodal analysis (MNA) stamps see
// http://www.analog-electronics.eu/analog-electronics/modified-nodal-analysis/modified-nodal-analysis.xhtml
-var cktsim = (function () {
+const cktsim = (function () {
///////////////////////////////////////////////////////////////////////////////
//
// Circuit
@@ -1978,7 +1979,7 @@ function update_schematics() {
}
window.update_schematics = update_schematics;
-schematic = (function () {
+const schematic = (function () {
var background_style = "rgb(220,220,220)";
var element_style = "rgb(255,255,255)";
var thumb_style = "rgb(128,128,128)";
@@ -6289,3 +6290,4 @@ schematic = (function () {
};
return module;
})();
+
diff --git a/xblocks_contrib/problem/assets/static/js/xmodule.js b/xblocks_contrib/problem/assets/static/js/xmodule.js
index 3dd386df..e56b7c76 100644
--- a/xblocks_contrib/problem/assets/static/js/xmodule.js
+++ b/xblocks_contrib/problem/assets/static/js/xmodule.js
@@ -1,103 +1,92 @@
-(function() {
- 'use strict';
-
- var XModule = {};
-
- XModule.Descriptor = (function() {
- /*
- * Bind the module to an element. This may be called multiple times,
- * if the element content has changed and so the module needs to be rebound
- *
- * @method: constructor
- * @param {html element} the .xmodule_edit section containing all of the descriptor content
- */
- var Descriptor = function(element) {
- this.element = element;
- this.update = _.bind(this.update, this);
- };
-
- /*
- * Register a callback method to be called when the state of this
- * descriptor is updated. The callback will be passed the results
- * of calling the save method on this descriptor.
- */
- Descriptor.prototype.onUpdate = function(callback) {
- if (!this.callbacks) {
- this.callbacks = [];
- }
-
- this.callbacks.push(callback);
- };
-
- /*
- * Notify registered callbacks that the state of this descriptor has changed
- */
- Descriptor.prototype.update = function() {
- var data, callbacks, i, length;
-
- data = this.save();
- callbacks = this.callbacks;
- length = callbacks.length;
-
- $.each(callbacks, function(index, callback) {
- callback(data);
- });
- };
-
- /*
- * Return the current state of the descriptor (to be written to the module store)
- *
- * @method: save
- * @returns {object} An object containing children and data attributes (both optional).
- * The contents of the attributes will be saved to the server
- */
- Descriptor.prototype.save = function() {
- return {};
- };
-
- return Descriptor;
- }());
-
- this.XBlockToXModuleShim = function(runtime, element, initArgs) {
- /*
- * Load a single module (either an edit module or a display module)
- * from the supplied element, which should have a data-type attribute
- * specifying the class to load
- */
- var moduleType, module;
-
- if (initArgs) {
- moduleType = initArgs['xmodule-type'];
- }
- if (!moduleType) {
- moduleType = $(element).data('type');
- }
-
- if (moduleType === 'None') {
- return;
- }
-
- try {
- module = new window[moduleType](element, runtime);
-
- if ($(element).hasClass('xmodule_edit')) {
- $(document).trigger('XModule.loaded.edit', [element, module]);
- }
-
- if ($(element).hasClass('xmodule_display')) {
- $(document).trigger('XModule.loaded.display', [element, module]);
- }
-
- return module;
- } catch (error) {
- console.error('Unable to load ' + moduleType + ': ' + error.message);
- }
- };
-
- // Export this module. We do it at the end when everything is ready
- // because some RequireJS scripts require this module. If
- // `window.XModule` appears as defined before this file has a chance
- // to execute fully, then there is a chance that RequireJS will execute
- // some script prematurely.
- this.XModule = XModule;
-}).call(this);
+const XModule = {};
+
+XModule.Descriptor = (function () {
+ /*
+ * Bind the module to an element. This may be called multiple times,
+ * if the element content has changed and so the module needs to be rebound
+ *
+ * @method: constructor
+ * @param {html element} the .xmodule_edit section containing all of the descriptor content
+ */
+ const Descriptor = function (element) {
+ this.element = element;
+ this.update = _.bind(this.update, this);
+ };
+
+ /*
+ * Register a callback method to be called when the state of this
+ * descriptor is updated. The callback will be passed the results
+ * of calling the save method on this descriptor.
+ */
+ Descriptor.prototype.onUpdate = function (callback) {
+ if (!this.callbacks) {
+ this.callbacks = [];
+ }
+
+ this.callbacks.push(callback);
+ };
+
+ /*
+ * Notify registered callbacks that the state of this descriptor has changed
+ */
+ Descriptor.prototype.update = function () {
+ const data = this.save();
+ const callbacks = this.callbacks;
+
+ $.each(callbacks, function (index, callback) {
+ callback(data);
+ });
+ };
+
+ /*
+ * Return the current state of the descriptor (to be written to the module store)
+ *
+ * @method: save
+ * @returns {object} An object containing children and data attributes (both optional).
+ * The contents of the attributes will be saved to the server
+ */
+ Descriptor.prototype.save = function () {
+ return {};
+ };
+
+ return Descriptor;
+}());
+
+const XBlockToXModuleShim = function (runtime, element, initArgs) {
+ /*
+ * Load a single module (either an edit module or a display module)
+ * from the supplied element, which should have a data-type attribute
+ * specifying the class to load
+ */
+ let moduleType;
+
+ if (initArgs) {
+ moduleType = initArgs['xmodule-type'];
+ }
+ if (!moduleType) {
+ moduleType = $(element).data('type');
+ }
+
+ if (moduleType === 'None') {
+ return;
+ }
+
+ try {
+ const module = new window[moduleType](element, runtime);
+
+ if ($(element).hasClass('xmodule_edit')) {
+ $(document).trigger('XModule.loaded.edit', [element, module]);
+ }
+
+ if ($(element).hasClass('xmodule_display')) {
+ $(document).trigger('XModule.loaded.display', [element, module]);
+ }
+
+ return module;
+ } catch (error) {
+ console.error(`Unable to load ${moduleType}: ${error.message}`);
+ }
+};
+
+window.XModule = XModule;
+window.XBlockToXModuleShim = XBlockToXModuleShim;
diff --git a/xblocks_contrib/problem/assets/webpack.config.js b/xblocks_contrib/problem/assets/webpack.config.js
index 82337c79..be2e70f6 100644
--- a/xblocks_contrib/problem/assets/webpack.config.js
+++ b/xblocks_contrib/problem/assets/webpack.config.js
@@ -105,68 +105,10 @@ module.exports = {
test: /\.(js|jsx)$/,
exclude: [
/node_modules/,
- /\/static\/js\/(?!vendor\/codemirror-compressed\.js)/
+ /\/static\/js\/(?!(display|imageinput|schematic|collapsible|javascript_loader|xmodule)\.js|vendor\/codemirror-compressed\.js)/
],
use: 'babel-loader'
},
- {
- test: /static\/js\/display.js/,
- use: [
- {
- loader: 'imports-loader',
- options: 'this=>window'
- }
- ]
- },
- {
- test: /static\/js\/imageinput.js/,
- use: [
- {
- loader: 'imports-loader',
- options: 'this=>window'
- }
- ]
- },
- {
- test: /static\/js\/schematic.js/,
- use: [
- {
- loader: 'imports-loader',
- options: 'this=>window'
- }
- ]
- },
- {
- test: /static\/js\/collapsible.js/,
- use: [
- {
- loader: 'imports-loader',
- options: 'this=>window'
- }
- ]
- },
- {
- test: /static\/js\/javascript_loader.js/,
- use: [
- {
- loader: 'imports-loader',
- options: 'this=>window'
- }
- ]
- },
- {
- test: /static\/js\/xmodule.js/,
- use: [
- {
- loader: 'exports-loader',
- options: 'window.XModule'
- },
- {
- loader: 'imports-loader',
- options: 'this=>window'
- }
- ]
- },
{
test: /codemirror/,
use: [