Commit 1b03a009 by cahrens

Replace with CoffeeScript generated version of JS.

Includes reformatting to 4 spaces.
parent 7a443737
...@@ -549,7 +549,7 @@ class JavascriptInput(InputTypeBase): ...@@ -549,7 +549,7 @@ class JavascriptInput(InputTypeBase):
TODO (arjun?): document this in detail. Initial notes: TODO (arjun?): document this in detail. Initial notes:
- display_class is a subclass of XProblemClassDisplay (see - display_class is a subclass of XProblemClassDisplay (see
xmodule/xmodule/js/src/capa/display.coffee), xmodule/xmodule/js/src/capa/display.js),
- display_file is the js script to be in /static/js/ where display_class is defined. - display_file is the js script to be in /static/js/ where display_class is defined.
""" """
......
...@@ -29,11 +29,9 @@ class CapaModule(CapaMixin, XModule): ...@@ -29,11 +29,9 @@ class CapaModule(CapaMixin, XModule):
icon_class = 'problem' icon_class = 'problem'
js = { js = {
'coffee': [
resource_string(__name__, 'js/src/capa/display.coffee'),
],
'js': [ 'js': [
resource_string(__name__, 'js/src/javascript_loader.js'), resource_string(__name__, 'js/src/javascript_loader.js'),
resource_string(__name__, 'js/src/capa/display.js'),
resource_string(__name__, 'js/src/collapsible.js'), resource_string(__name__, 'js/src/collapsible.js'),
resource_string(__name__, 'js/src/capa/imageinput.js'), resource_string(__name__, 'js/src/capa/imageinput.js'),
resource_string(__name__, 'js/src/capa/schematic.js'), resource_string(__name__, 'js/src/capa/schematic.js'),
......
class @Problem // Generated by CoffeeScript 1.6.1
(function () {
constructor: (element) -> var _this = this,
@el = $(element).find('.problems-wrapper') __indexOf = [].indexOf || function (item) {
@id = @el.data('problem-id') for (var i = 0, l = this.length; i < l; i++) {
@element_id = @el.attr('id') if (i in this && this[i] === item) return i;
@url = @el.data('url') }
@content = @el.data('content') return -1;
};
# has_timed_out and has_response are used to ensure that are used to
# ensure that we wait a minimum of ~ 1s before transitioning the submit this.Problem = (function () {
# button from disabled to enabled var _this = this;
@has_timed_out = false
@has_response = false function Problem(element) {
var _this = this;
@render(@content) this.hint_button = function () {
return Problem.prototype.hint_button.apply(_this, arguments);
$: (selector) -> };
$(selector, @el) this.enableSubmitButtonAfterTimeout = function () {
return Problem.prototype.enableSubmitButtonAfterTimeout.apply(_this, arguments);
bind: => };
if MathJax? this.enableSubmitButtonAfterResponse = function () {
@el.find('.problem > div').each (index, element) => return Problem.prototype.enableSubmitButtonAfterResponse.apply(_this, arguments);
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element] };
this.enableSubmitButton = function (enable, changeText) {
window.update_schematics() if (changeText == null) {
changeText = true;
problem_prefix = @element_id.replace(/problem_/,'') }
@inputs = @$("[id^='input_#{problem_prefix}_']") return Problem.prototype.enableSubmitButton.apply(_this, arguments);
@$('div.action button').click @refreshAnswers };
@reviewButton = @$('.notification-btn.review-btn') this.enableAllButtons = function (enable, isFromCheckOperation) {
@reviewButton.click @scroll_to_problem_meta return Problem.prototype.enableAllButtons.apply(_this, arguments);
@submitButton = @$('.action .submit') };
@submitButtonLabel = @$('.action .submit .submit-label') this.disableAllButtonsWhileRunning = function (operationCallback, isFromCheckOperation) {
@submitButtonSubmitText = @submitButtonLabel.text() return Problem.prototype.disableAllButtonsWhileRunning.apply(_this, arguments);
@submitButtonSubmittingText = @submitButton.data('submitting') };
@submitButton.click @submit_fd this.submitAnswersAndSubmitButton = function (bind) {
@hintButton = @$('.action .hint-button') if (bind == null) {
@hintButton.click @hint_button bind = false;
@resetButton = @$('.action .reset') }
@resetButton.click @reset return Problem.prototype.submitAnswersAndSubmitButton.apply(_this, arguments);
@showButton = @$('.action .show') };
@showButton.click @show this.refreshAnswers = function () {
@saveButton = @$('.action .save') return Problem.prototype.refreshAnswers.apply(_this, arguments);
@saveNotification = @$('.notification-save') };
@saveButtonLabel = @$('.action .save .save-label') this.updateMathML = function (jax, element) {
@saveButton.click @save return Problem.prototype.updateMathML.apply(_this, arguments);
@gentleAlertNotification = @$('.notification-gentle-alert') };
@submitNotification = @$('.notification-submit') this.refreshMath = function (event, element) {
return Problem.prototype.refreshMath.apply(_this, arguments);
# Accessibility helper for sighted keyboard users to show <clarification> tooltips on focus: };
@$('.clarification').focus (ev) => this.save_internal = function () {
icon = $(ev.target).children "i" return Problem.prototype.save_internal.apply(_this, arguments);
window.globalTooltipManager.openTooltip icon };
@$('.clarification').blur (ev) => this.save = function () {
window.globalTooltipManager.hide() return Problem.prototype.save.apply(_this, arguments);
};
@$('.review-btn').focus (ev) => this.gentle_alert = function (msg) {
$(ev.target).removeClass('sr'); return Problem.prototype.gentle_alert.apply(_this, arguments);
};
@$('.review-btn').blur (ev) => this.clear_all_notifications = function () {
$(ev.target).addClass('sr'); return Problem.prototype.clear_all_notifications.apply(_this, arguments);
};
@bindResetCorrectness() this.show = function () {
if @submitButton.length return Problem.prototype.show.apply(_this, arguments);
@submitAnswersAndSubmitButton true };
this.reset_internal = function () {
# Collapsibles return Problem.prototype.reset_internal.apply(_this, arguments);
Collapsible.setCollapsibles(@el) };
this.reset = function () {
# Dynamath return Problem.prototype.reset.apply(_this, arguments);
@$('input.math').keyup(@refreshMath) };
if MathJax? this.get_sr_status = function (contents) {
@$('input.math').each (index, element) => return Problem.prototype.get_sr_status.apply(_this, arguments);
MathJax.Hub.Queue [@refreshMath, null, element] };
this.submit_internal = function () {
renderProgressState: => return Problem.prototype.submit_internal.apply(_this, arguments);
detail = @el.data('progress_detail') };
status = @el.data('progress_status') this.submit = function () {
graded = @el.data('graded') return Problem.prototype.submit.apply(_this, arguments);
};
# Render 'x/y point(s)' if student has attempted question this.submit_fd = function () {
if status != 'none' and detail? and (jQuery.type(detail) == "string") and detail.indexOf('/') > 0 return Problem.prototype.submit_fd.apply(_this, arguments);
a = detail.split('/') };
earned = parseFloat(a[0]) this.focus_on_save_notification = function () {
possible = parseFloat(a[1]) return Problem.prototype.focus_on_save_notification.apply(_this, arguments);
};
if graded == "True" and possible != 0 this.focus_on_hint_notification = function () {
# This comment needs to be on one line to be properly scraped for the translators. Sry for length. return Problem.prototype.focus_on_hint_notification.apply(_this, arguments);
`// Translators: %(earned)s is the number of points earned. %(possible)s 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)` };
progress_template = ngettext('%(earned)s/%(possible)s point (graded)', '%(earned)s/%(possible)s points (graded)', possible) this.focus_on_submit_notification = function () {
else return Problem.prototype.focus_on_submit_notification.apply(_this, arguments);
# This comment needs to be on one line to be properly scraped for the translators. Sry for length. };
`// Translators: %(earned)s is the number of points earned. %(possible)s 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)` this.focus_on_notification = function (type) {
progress_template = ngettext('%(earned)s/%(possible)s point (ungraded)', '%(earned)s/%(possible)s points (ungraded)', possible) return Problem.prototype.focus_on_notification.apply(_this, arguments);
progress = interpolate(progress_template, {'earned': earned, 'possible': possible}, true) };
this.scroll_to_problem_meta = function () {
# Render 'x point(s) possible' if student has not yet attempted question return Problem.prototype.scroll_to_problem_meta.apply(_this, arguments);
# Status is set to none when a user has a score of 0, and 0 when the problem has a weight of 0. };
if status == 'none' or status == 0 this.submit_save_waitfor = function (callback) {
if detail? and (jQuery.type(detail) == "string") and detail.indexOf('/') > 0 return Problem.prototype.submit_save_waitfor.apply(_this, arguments);
a = detail.split('/') };
possible = parseFloat(a[1]) this.setupInputTypes = function () {
else return Problem.prototype.setupInputTypes.apply(_this, arguments);
possible = 0 };
this.poll = function (prev_timeout, focus_callback) {
if graded == "True" and possible != 0 return Problem.prototype.poll.apply(_this, arguments);
`// Translators: %(num_points)s is the number of points possible (examples: 1, 3, 10).` };
progress_template = ngettext("%(num_points)s point possible (graded)", "%(num_points)s points possible (graded)", possible) this.queueing = function (focus_callback) {
else return Problem.prototype.queueing.apply(_this, arguments);
`// Translators: %(num_points)s is the number of points possible (examples: 1, 3, 10).` };
progress_template = ngettext("%(num_points)s point possible (ungraded)", "%(num_points)s points possible (ungraded)", possible) this.forceUpdate = function (response) {
progress = interpolate(progress_template, {'num_points': possible}, true) return Problem.prototype.forceUpdate.apply(_this, arguments);
};
@$('.problem-progress').text(progress) this.updateProgress = function (response) {
return Problem.prototype.updateProgress.apply(_this, arguments);
updateProgress: (response) => };
if response.progress_changed this.renderProgressState = function () {
@el.data('progress_status', response.progress_status) return Problem.prototype.renderProgressState.apply(_this, arguments);
@el.data('progress_detail', response.progress_detail) };
@el.trigger('progressChanged') this.bind = function () {
@renderProgressState() return Problem.prototype.bind.apply(_this, arguments);
};
forceUpdate: (response) => this.el = $(element).find('.problems-wrapper');
@el.data('progress_status', response.progress_status) this.id = this.el.data('problem-id');
@el.data('progress_detail', response.progress_detail) this.element_id = this.el.attr('id');
@el.trigger('progressChanged') this.url = this.el.data('url');
@renderProgressState() this.content = this.el.data('content');
this.has_timed_out = false;
queueing: (focus_callback) => this.has_response = false;
@queued_items = @$(".xqueue") this.render(this.content);
@num_queued_items = @queued_items.length }
if @num_queued_items > 0
if window.queuePollerID # Only one poller 'thread' per Problem Problem.prototype.$ = function (selector) {
window.clearTimeout(window.queuePollerID) return $(selector, this.el);
window.queuePollerID = window.setTimeout( };
=> @poll(1000, focus_callback),
1000) Problem.prototype.bind = function () {
var problem_prefix,
poll: (prev_timeout, focus_callback) => _this = this;
$.postWithPrefix "#{@url}/problem_get", (response) => if (typeof MathJax !== "undefined" && MathJax !== null) {
# If queueing status changed, then render this.el.find('.problem > div').each(function (index, element) {
@new_queued_items = $(response.html).find(".xqueue") return MathJax.Hub.Queue(["Typeset", MathJax.Hub, element]);
if @new_queued_items.length isnt @num_queued_items });
edx.HtmlUtils.setHtml(@el, edx.HtmlUtils.HTML(response.html)).promise().done => }
focus_callback?() window.update_schematics();
JavascriptLoader.executeModuleScripts @el, () => problem_prefix = this.element_id.replace(/problem_/, '');
@setupInputTypes() this.inputs = this.$("[id^='input_" + problem_prefix + "_']");
@bind() this.$('div.action button').click(this.refreshAnswers);
this.reviewButton = this.$('.notification-btn.review-btn');
@num_queued_items = @new_queued_items.length this.reviewButton.click(this.scroll_to_problem_meta);
if @num_queued_items == 0 this.submitButton = this.$('.action .submit');
@forceUpdate response this.submitButtonLabel = this.$('.action .submit .submit-label');
delete window.queuePollerID this.submitButtonSubmitText = this.submitButtonLabel.text();
else this.submitButtonSubmittingText = this.submitButton.data('submitting');
new_timeout = prev_timeout * 2 this.submitButton.click(this.submit_fd);
# if the timeout is greather than 1 minute this.hintButton = this.$('.action .hint-button');
if new_timeout >= 60000 this.hintButton.click(this.hint_button);
delete window.queuePollerID this.resetButton = this.$('.action .reset');
@gentle_alert gettext("The grading process is still running. Refresh the page to see updates.") this.resetButton.click(this.reset);
else this.showButton = this.$('.action .show');
window.queuePollerID = window.setTimeout( this.showButton.click(this.show);
=> @poll(new_timeout, focus_callback), this.saveButton = this.$('.action .save');
new_timeout this.saveNotification = this.$('.notification-save');
) this.saveButtonLabel = this.$('.action .save .save-label');
this.saveButton.click(this.save);
this.gentleAlertNotification = this.$('.notification-gentle-alert');
# Use this if you want to make an ajax call on the input type object this.submitNotification = this.$('.notification-submit');
# static method so you don't have to instantiate a Problem in order to use it this.$('.clarification').focus(function (ev) {
# Input: var icon;
# url: the AJAX url of the problem icon = $(ev.target).children("i");
# input_id: the input_id of the input you would like to make the call on return window.globalTooltipManager.openTooltip(icon);
# 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 this.$('.clarification').blur(function (ev) {
# finding the correct input return window.globalTooltipManager.hide();
# dispatch: string that indicates how this data should be handled by the inputtype });
# callback: the function that will be called once the AJAX call has been completed. this.$('.review-btn').focus(function (ev) {
# It will be passed a response object return $(ev.target).removeClass('sr');
@inputAjax: (url, input_id, dispatch, data, callback) -> });
data['dispatch'] = dispatch this.$('.review-btn').blur(function (ev) {
data['input_id'] = input_id return $(ev.target).addClass('sr');
$.postWithPrefix "#{url}/input_ajax", data, callback });
this.bindResetCorrectness();
if (this.submitButton.length) {
render: (content, focus_callback) -> this.submitAnswersAndSubmitButton(true);
if content }
@el.html(content) Collapsible.setCollapsibles(this.el);
JavascriptLoader.executeModuleScripts @el, () => this.$('input.math').keyup(this.refreshMath);
@setupInputTypes() if (typeof MathJax !== "undefined" && MathJax !== null) {
@bind() return this.$('input.math').each(function (index, element) {
@queueing(focus_callback) return MathJax.Hub.Queue([_this.refreshMath, null, element]);
@renderProgressState() });
focus_callback?() }
else };
$.postWithPrefix "#{@url}/problem_get", (response) =>
@el.html(response.html) Problem.prototype.renderProgressState = function () {
JavascriptLoader.executeModuleScripts @el, () => var a, detail, earned, graded, possible, progress, progress_template, status;
@setupInputTypes() detail = this.el.data('progress_detail');
@bind() status = this.el.data('progress_status');
@queueing() graded = this.el.data('graded');
@forceUpdate response if (status !== 'none' && (detail != null) && (jQuery.type(detail) === "string") && detail.indexOf('/') > 0) {
a = detail.split('/');
# TODO add hooks for problem types here by inspecting response.html and doing earned = parseFloat(a[0]);
# stuff if a div w a class is found possible = parseFloat(a[1]);
if (graded === "True" && possible !== 0) {
setupInputTypes: => // Translators: %(earned)s is the number of points earned. %(possible)s 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);
@inputtypeDisplays = {} progress_template = ngettext('%(earned)s/%(possible)s point (graded)', '%(earned)s/%(possible)s points (graded)', possible);
@el.find(".capa_inputtype").each (index, inputtype) => } else {
classes = $(inputtype).attr('class').split(' ') // Translators: %(earned)s is the number of points earned. %(possible)s 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);
id = $(inputtype).attr('id') progress_template = ngettext('%(earned)s/%(possible)s point (ungraded)', '%(earned)s/%(possible)s points (ungraded)', possible);
for cls in classes }
setupMethod = @inputtypeSetupMethods[cls] progress = interpolate(progress_template, {
if setupMethod? 'earned': earned,
@inputtypeDisplays[id] = setupMethod(inputtype) 'possible': possible
}, true);
# If some function wants to be called before sending the answer to the }
# server, give it a chance to do so. if (status === 'none' || status === 0) {
# if ((detail != null) && (jQuery.type(detail) === "string") && detail.indexOf('/') > 0) {
# submit_save_waitfor allows the callee to send alerts if the user's input is a = detail.split('/');
# invalid. To do so, the callee must throw an exception named "Waitfor possible = parseFloat(a[1]);
# Exception". This and any other errors or exceptions that arise from the } else {
# callee are rethrown and abort the submission. possible = 0;
# }
# In order to use this feature, add a 'data-waitfor' attribute to the input, if (graded === "True" && possible !== 0) {
# and specify the function to be called by the submit button before sending // Translators: %(num_points)s is the number of points possible (examples: 1, 3, 10).;
# off @answers progress_template = ngettext("%(num_points)s point possible (graded)", "%(num_points)s points possible (graded)", possible);
submit_save_waitfor: (callback) => } else {
flag = false // Translators: %(num_points)s is the number of points possible (examples: 1, 3, 10).;
for inp in @inputs progress_template = ngettext("%(num_points)s point possible (ungraded)", "%(num_points)s points possible (ungraded)", possible);
if ($(inp).is("input[waitfor]")) }
try progress = interpolate(progress_template, {
$(inp).data("waitfor")(() => 'num_points': possible
@refreshAnswers() }, true);
callback() }
) return this.$('.problem-progress').text(progress);
catch e };
if e.name == "Waitfor Exception"
alert e.message Problem.prototype.updateProgress = function (response) {
else if (response.progress_changed) {
alert "Could not grade your answer. The submission was aborted." this.el.data('progress_status', response.progress_status);
throw e this.el.data('progress_detail', response.progress_detail);
flag = true this.el.trigger('progressChanged');
else }
flag = false return this.renderProgressState();
return flag };
# Scroll to problem metadata and next focus is problem input Problem.prototype.forceUpdate = function (response) {
scroll_to_problem_meta: => this.el.data('progress_status', response.progress_status);
questionTitle = @$(".problem-header") this.el.data('progress_detail', response.progress_detail);
if questionTitle.length > 0 this.el.trigger('progressChanged');
return this.renderProgressState();
};
Problem.prototype.queueing = function (focus_callback) {
var _this = this;
this.queued_items = this.$(".xqueue");
this.num_queued_items = this.queued_items.length;
if (this.num_queued_items > 0) {
if (window.queuePollerID) {
window.clearTimeout(window.queuePollerID);
}
return window.queuePollerID = window.setTimeout(function () {
return _this.poll(1000, focus_callback);
}, 1000);
}
};
Problem.prototype.poll = function (prev_timeout, focus_callback) {
var _this = this;
return $.postWithPrefix("" + this.url + "/problem_get", function (response) {
var new_timeout;
_this.new_queued_items = $(response.html).find(".xqueue");
if (_this.new_queued_items.length !== _this.num_queued_items) {
edx.HtmlUtils.setHtml(_this.el, edx.HtmlUtils.HTML(response.html)).promise().done(function () {
return typeof focus_callback === "function" ? focus_callback() : void 0;
});
JavascriptLoader.executeModuleScripts(_this.el, function () {
_this.setupInputTypes();
return _this.bind();
});
}
_this.num_queued_items = _this.new_queued_items.length;
if (_this.num_queued_items === 0) {
_this.forceUpdate(response);
return delete window.queuePollerID;
} else {
new_timeout = prev_timeout * 2;
if (new_timeout >= 60000) {
delete window.queuePollerID;
return _this.gentle_alert(gettext("The grading process is still running. Refresh the page to see updates."));
} else {
return window.queuePollerID = window.setTimeout(function () {
return _this.poll(new_timeout, focus_callback);
}, new_timeout);
}
}
});
};
Problem.inputAjax = function (url, input_id, dispatch, data, callback) {
data['dispatch'] = dispatch;
data['input_id'] = input_id;
return $.postWithPrefix("" + url + "/input_ajax", data, callback);
};
Problem.prototype.render = function (content, focus_callback) {
var _this = this;
if (content) {
this.el.html(content);
return JavascriptLoader.executeModuleScripts(this.el, function () {
_this.setupInputTypes();
_this.bind();
_this.queueing(focus_callback);
_this.renderProgressState();
return typeof focus_callback === "function" ? focus_callback() : void 0;
});
} else {
return $.postWithPrefix("" + this.url + "/problem_get", function (response) {
_this.el.html(response.html);
return JavascriptLoader.executeModuleScripts(_this.el, function () {
_this.setupInputTypes();
_this.bind();
_this.queueing();
return _this.forceUpdate(response);
});
});
}
};
Problem.prototype.setupInputTypes = function () {
var _this = 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 = _this.inputtypeSetupMethods[cls];
if (setupMethod != null) {
_results.push(_this.inputtypeDisplays[id] = setupMethod(inputtype));
} else {
_results.push(void 0);
}
}
return _results;
});
};
Problem.prototype.submit_save_waitfor = function (callback) {
var flag, inp, _i, _len, _ref,
_this = 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 () {
_this.refreshAnswers();
return callback();
});
} catch (e) {
if (e.name === "Waitfor Exception") {
alert(e.message);
} else {
alert("Could not grade your answer. The submission was aborted.");
}
throw e;
}
flag = true;
} else {
flag = false;
}
}
return flag;
};
Problem.prototype.scroll_to_problem_meta = function () {
var questionTitle;
questionTitle = this.$(".problem-header");
if (questionTitle.length > 0) {
$('html, body').animate({ $('html, body').animate({
scrollTop: questionTitle.offset().top scrollTop: questionTitle.offset().top
}, 500); }, 500);
questionTitle.focus() return questionTitle.focus();
}
};
focus_on_notification: (type) => Problem.prototype.focus_on_notification = function (type) {
notification = @$('.notification-'+type) var notification;
if notification.length > 0 notification = this.$('.notification-' + type);
notification.focus() if (notification.length > 0) {
return notification.focus();
}
};
focus_on_submit_notification: => Problem.prototype.focus_on_submit_notification = function () {
@focus_on_notification('submit') return this.focus_on_notification('submit');
};
focus_on_hint_notification: => Problem.prototype.focus_on_hint_notification = function () {
@focus_on_notification('hint') return this.focus_on_notification('hint');
};
focus_on_save_notification: => Problem.prototype.focus_on_save_notification = function () {
@focus_on_notification('save') return this.focus_on_notification('save');
};
### /*
# 'submit_fd' uses FormData to allow file submissions in the 'problem_check' dispatch, # 'submit_fd' uses FormData to allow file submissions in the 'problem_check' dispatch,
# in addition to simple querystring-based answers # in addition to simple querystring-based answers
# #
# NOTE: The dispatch 'problem_check' is being singled out for the use of FormData; # NOTE: The dispatch 'problem_check' is being singled out for the use of FormData;
# maybe preferable to consolidate all dispatches to use FormData # maybe preferable to consolidate all dispatches to use FormData
### */
submit_fd: =>
# If there are no file inputs in the problem, we can fall back on @submit
if @el.find('input:file').length == 0 Problem.prototype.submit_fd = function () {
@submit() var abort_submission, error, error_html, errors, fd, file_not_selected, file_too_large, max_filesize, required_files_not_submitted, settings, timeout_id, unallowed_file_submitted, _i, _len,
return _this = this;
if (this.el.find('input:file').length === 0) {
@enableSubmitButton false this.submit();
return;
if not window.FormData }
alert "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." this.enableSubmitButton(false);
@enableSubmitButton true if (!window.FormData) {
return alert("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.");
this.enableSubmitButton(true);
timeout_id = @enableSubmitButtonAfterTimeout() return;
}
fd = new FormData() timeout_id = this.enableSubmitButtonAfterTimeout();
fd = new FormData();
# Sanity checks on submission max_filesize = 4 * 1000 * 1000;
max_filesize = 4*1000*1000 # 4 MB file_too_large = false;
file_too_large = false file_not_selected = false;
file_not_selected = false required_files_not_submitted = false;
required_files_not_submitted = false unallowed_file_submitted = false;
unallowed_file_submitted = false errors = [];
this.inputs.each(function (index, element) {
errors = [] var allowed_files, file, max_size, required_files, _i, _len, _ref, _ref1, _ref2;
if (element.type === 'file') {
@inputs.each (index, element) -> required_files = $(element).data("required_files");
if element.type is 'file' allowed_files = $(element).data("allowed_files");
required_files = $(element).data("required_files") _ref = element.files;
allowed_files = $(element).data("allowed_files") for (_i = 0, _len = _ref.length; _i < _len; _i++) {
for file in element.files file = _ref[_i];
if allowed_files.length != 0 and file.name not in allowed_files if (allowed_files.length !== 0 && (_ref1 = file.name, __indexOf.call(allowed_files, _ref1) < 0)) {
unallowed_file_submitted = true unallowed_file_submitted = true;
errors.push "You submitted #{file.name}; only #{allowed_files} are allowed." errors.push("You submitted " + file.name + "; only " + allowed_files + " are allowed.");
if file.name in required_files }
required_files.splice(required_files.indexOf(file.name), 1) if (_ref2 = file.name, __indexOf.call(required_files, _ref2) >= 0) {
if file.size > max_filesize required_files.splice(required_files.indexOf(file.name), 1);
file_too_large = true }
max_size = max_filesize / (1000*1000) if (file.size > max_filesize) {
errors.push "Your file #{file.name} is too large (max size: {max_size}MB)" file_too_large = true;
fd.append(element.id, file) max_size = max_filesize / (1000 * 1000);
if element.files.length == 0 errors.push("Your file " + file.name + " is too large (max size: {max_size}MB)");
file_not_selected = true }
fd.append(element.id, '') # In case we want to allow submissions with no file fd.append(element.id, file);
if required_files.length != 0 }
required_files_not_submitted = true if (element.files.length === 0) {
errors.push "You did not submit the required files: #{required_files}." file_not_selected = true;
else fd.append(element.id, '');
fd.append(element.id, element.value) }
if (required_files.length !== 0) {
required_files_not_submitted = true;
if file_not_selected return errors.push("You did not submit the required files: " + required_files + ".");
errors.push 'You did not select any files to submit' }
} else {
error_html = '<ul>\n' return fd.append(element.id, element.value);
for error in errors }
error_html += '<li>' + error + '</li>\n' });
error_html += '</ul>' if (file_not_selected) {
@gentle_alert error_html errors.push('You did not select any files to submit');
}
abort_submission = file_too_large or file_not_selected or unallowed_file_submitted or required_files_not_submitted error_html = '<ul>\n';
if abort_submission for (_i = 0, _len = errors.length; _i < _len; _i++) {
window.clearTimeout(timeout_id) error = errors[_i];
@enableSubmitButton true error_html += '<li>' + error + '</li>\n';
return }
error_html += '</ul>';
settings = this.gentle_alert(error_html);
type: "POST" abort_submission = file_too_large || file_not_selected || unallowed_file_submitted || required_files_not_submitted;
data: fd if (abort_submission) {
processData: false window.clearTimeout(timeout_id);
contentType: false this.enableSubmitButton(true);
complete: @enableSubmitButtonAfterResponse return;
success: (response) => }
switch response.success settings = {
when 'incorrect', 'correct' type: "POST",
@render(response.contents) data: fd,
@updateProgress response processData: false,
else contentType: false,
@gentle_alert response.success complete: this.enableSubmitButtonAfterResponse,
Logger.log 'problem_graded', [@answers, response.contents], @id success: function (response) {
switch (response.success) {
$.ajaxWithPrefix("#{@url}/problem_check", settings) case 'incorrect':
case 'correct':
submit: => _this.render(response.contents);
if not @submit_save_waitfor(@submit_internal) _this.updateProgress(response);
@disableAllButtonsWhileRunning @submit_internal, true break;
default:
submit_internal: => _this.gentle_alert(response.success);
Logger.log 'problem_check', @answers }
$.postWithPrefix "#{@url}/problem_check", @answers, (response) => return Logger.log('problem_graded', [_this.answers, response.contents], _this.id);
switch response.success }
when 'incorrect', 'correct' };
window.SR.readTexts(@get_sr_status(response.contents)) return $.ajaxWithPrefix("" + this.url + "/problem_check", settings);
@el.trigger('contentChanged', [@id, response.contents]) };
@render(response.contents, @focus_on_submit_notification)
@updateProgress response Problem.prototype.submit = function () {
else if (!this.submit_save_waitfor(this.submit_internal)) {
@saveNotification.hide() return this.disableAllButtonsWhileRunning(this.submit_internal, true);
@gentle_alert response.success }
Logger.log 'problem_graded', [@answers, response.contents], @id };
get_sr_status: (contents) => Problem.prototype.submit_internal = function () {
# This method builds up an array of strings to send to the page screen-reader span. var _this = this;
# It first gets all elements with class "status", and then looks to see if they are contained Logger.log('problem_check', this.answers);
# in sections with aria-labels. If so, labels are prepended to the status element text. return $.postWithPrefix("" + this.url + "/problem_check", this.answers, function (response) {
# If not, just the text of the status elements are returned. switch (response.success) {
status_elements = $(contents).find('.status') case 'incorrect':
labeled_status = [] case 'correct':
for element in status_elements window.SR.readTexts(_this.get_sr_status(response.contents));
parent_section = $(element).closest('section') _this.el.trigger('contentChanged', [_this.id, response.contents]);
added_status = false _this.render(response.contents, _this.focus_on_submit_notification);
if parent_section _this.updateProgress(response);
aria_label = parent_section.attr('aria-label') break;
if aria_label default:
`// Translators: This is only translated to allow for reording of label and associated status.` _this.saveNotification.hide();
template = gettext("{label}: {status}") _this.gentle_alert(response.success);
labeled_status.push(edx.StringUtils.interpolate(template, {label: aria_label, status: $(element).text()})) }
added_status = true return Logger.log('problem_graded', [_this.answers, response.contents], _this.id);
});
if not added_status };
labeled_status.push($(element).text())
Problem.prototype.get_sr_status = function (contents) {
return labeled_status var added_status, aria_label, element, labeled_status, parent_section, status_elements, template, _i, _len;
status_elements = $(contents).find('.status');
reset: => labeled_status = [];
@disableAllButtonsWhileRunning @reset_internal, false for (_i = 0, _len = status_elements.length; _i < _len; _i++) {
element = status_elements[_i];
reset_internal: => parent_section = $(element).closest('section');
Logger.log 'problem_reset', @answers added_status = false;
$.postWithPrefix "#{@url}/problem_reset", id: @id, (response) => if (parent_section) {
if response.success aria_label = parent_section.attr('aria-label');
@el.trigger('contentChanged', [@id, response.html]) if (aria_label) {
@render(response.html, @scroll_to_problem_meta) // Translators: This is only translated to allow for reording of label and associated status.;
@updateProgress response template = gettext("{label}: {status}");
window.SR.readText(gettext('This problem has been reset.')) labeled_status.push(edx.StringUtils.interpolate(template, {
else label: aria_label,
@gentle_alert response.msg status: $(element).text()
}));
# TODO this needs modification to deal with javascript responses; perhaps we added_status = true;
# need something where responsetypes can define their own behavior when show }
# is called. }
show: => if (!added_status) {
Logger.log 'problem_show', problem: @id labeled_status.push($(element).text());
$.postWithPrefix "#{@url}/problem_show", (response) => }
answers = response.answers }
$.each answers, (key, value) => return labeled_status;
if $.isArray(value) };
for choice in value
@$("label[for='input_#{key}_#{choice}']").attr correct_answer: 'true' Problem.prototype.reset = function () {
else return this.disableAllButtonsWhileRunning(this.reset_internal, false);
answer = @$("#answer_#{key}, #solution_#{key}") };
edx.HtmlUtils.setHtml(answer, edx.HtmlUtils.HTML(value))
Collapsible.setCollapsibles(answer) Problem.prototype.reset_internal = function () {
var _this = this;
# Sometimes, `value` is just a string containing a MathJax formula. Logger.log('problem_reset', this.answers);
# If this is the case, jQuery will throw an error in some corner cases return $.postWithPrefix("" + this.url + "/problem_reset", {
# because of an incorrect selector. We setup a try..catch so that id: this.id
# the script doesn't break in such cases. }, function (response) {
# if (response.success) {
# We will fallback to the second `if statement` below, if an _this.el.trigger('contentChanged', [_this.id, response.html]);
# error is thrown by jQuery. _this.render(response.html, _this.scroll_to_problem_meta);
try _this.updateProgress(response);
solution = $(value).find('.detailed-solution') return window.SR.readText(gettext('This problem has been reset.'));
catch e } else {
solution = {} return _this.gentle_alert(response.msg);
}
# TODO remove the above once everything is extracted into its own });
# inputtype functions. };
@el.find(".capa_inputtype").each (index, inputtype) => Problem.prototype.show = function () {
classes = $(inputtype).attr('class').split(' ') var _this = this;
for cls in classes Logger.log('problem_show', {
display = @inputtypeDisplays[$(inputtype).attr('id')] problem: this.id
showMethod = @inputtypeShowAnswerMethods[cls] });
showMethod(inputtype, display, answers) if showMethod? return $.postWithPrefix("" + this.url + "/problem_show", function (response) {
var answers;
if MathJax? answers = response.answers;
@el.find('.problem > div').each (index, element) => $.each(answers, function (key, value) {
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element] var answer, choice, solution, _i, _len, _results;
if ($.isArray(value)) {
@el.find('.show').attr('disabled', 'disabled') _results = [];
@updateProgress response for (_i = 0, _len = value.length; _i < _len; _i++) {
window.SR.readText(gettext('Answers to this problem are now shown. Navigate through the problem to review it with answers inline.')) choice = value[_i];
@scroll_to_problem_meta() _results.push(_this.$("label[for='input_" + key + "_" + choice + "']").attr({
correct_answer: 'true'
clear_all_notifications: => }));
@submitNotification.remove() }
@gentleAlertNotification.hide() return _results;
@saveNotification.hide() } else {
answer = _this.$("#answer_" + key + ", #solution_" + key);
gentle_alert: (msg) => edx.HtmlUtils.setHtml(answer, edx.HtmlUtils.HTML(value));
edx.HtmlUtils.setHtml(@el.find('.notification-gentle-alert .notification-message'), edx.HtmlUtils.HTML(msg)) Collapsible.setCollapsibles(answer);
@clear_all_notifications() try {
@gentleAlertNotification.show() return solution = $(value).find('.detailed-solution');
@gentleAlertNotification.focus() } catch (e) {
return solution = {};
save: => }
if not @submit_save_waitfor(@save_internal) }
@disableAllButtonsWhileRunning @save_internal, false });
_this.el.find(".capa_inputtype").each(function (index, inputtype) {
save_internal: => var classes, cls, display, showMethod, _i, _len, _results;
Logger.log 'problem_save', @answers classes = $(inputtype).attr('class').split(' ');
$.postWithPrefix "#{@url}/problem_save", @answers, (response) => _results = [];
saveMessage = response.msg for (_i = 0, _len = classes.length; _i < _len; _i++) {
if response.success cls = classes[_i];
@el.trigger('contentChanged', [@id, response.html]) display = _this.inputtypeDisplays[$(inputtype).attr('id')];
edx.HtmlUtils.setHtml(@el.find('.notification-save .notification-message'), edx.HtmlUtils.HTML(saveMessage)) showMethod = _this.inputtypeShowAnswerMethods[cls];
@clear_all_notifications() if (showMethod != null) {
@saveNotification.show() _results.push(showMethod(inputtype, display, answers));
@focus_on_save_notification() } else {
else _results.push(void 0);
@gentle_alert saveMessage }
}
refreshMath: (event, element) => return _results;
element = event.target unless element });
elid = element.id.replace(/^input_/,'') if (typeof MathJax !== "undefined" && MathJax !== null) {
target = "display_" + elid _this.el.find('.problem > div').each(function (index, element) {
return MathJax.Hub.Queue(["Typeset", MathJax.Hub, element]);
# MathJax preprocessor is loaded by 'setupInputTypes' });
preprocessor_tag = "inputtype_" + elid }
mathjax_preprocessor = @inputtypeDisplays[preprocessor_tag] _this.el.find('.show').attr('disabled', 'disabled');
_this.updateProgress(response);
if MathJax? and jax = MathJax.Hub.getAllJax(target)[0] window.SR.readText(gettext('Answers to this problem are now shown. Navigate through the problem to review it with answers inline.'));
eqn = $(element).val() return _this.scroll_to_problem_meta();
if mathjax_preprocessor });
eqn = mathjax_preprocessor(eqn) };
MathJax.Hub.Queue(['Text', jax, eqn], [@updateMathML, jax, element])
Problem.prototype.clear_all_notifications = function () {
return # Explicit return for CoffeeScript this.submitNotification.remove();
this.gentleAlertNotification.hide();
updateMathML: (jax, element) => return this.saveNotification.hide();
try };
$("##{element.id}_dynamath").val(jax.root.toMathML '')
catch exception Problem.prototype.gentle_alert = function (msg) {
throw exception unless exception.restart edx.HtmlUtils.setHtml(this.el.find('.notification-gentle-alert .notification-message'), edx.HtmlUtils.HTML(msg));
if MathJax? this.clear_all_notifications();
MathJax.Callback.After [@refreshMath, jax], exception.restart this.gentleAlertNotification.show();
return this.gentleAlertNotification.focus();
refreshAnswers: => };
@$('input.schematic').each (index, element) ->
element.schematic.update_value() Problem.prototype.save = function () {
@$(".CodeMirror").each (index, element) -> if (!this.submit_save_waitfor(this.save_internal)) {
element.CodeMirror.save() if element.CodeMirror.save return this.disableAllButtonsWhileRunning(this.save_internal, false);
@answers = @inputs.serialize() }
};
submitAnswersAndSubmitButton: (bind=false) =>
""" Problem.prototype.save_internal = function () {
Used to check available answers and if something is checked (or the answer is set in some textbox) var _this = this;
"Submit" button becomes enabled. Otherwise it is disabled by default. Logger.log('problem_save', this.answers);
return $.postWithPrefix("" + this.url + "/problem_save", this.answers, function (response) {
Arguments: var saveMessage;
bind (bool): used on the first check to attach event handlers to input fields saveMessage = response.msg;
to change "Submit" enable status in case of some manipulations with answers if (response.success) {
""" _this.el.trigger('contentChanged', [_this.id, response.html]);
answered = true edx.HtmlUtils.setHtml(_this.el.find('.notification-save .notification-message'), edx.HtmlUtils.HTML(saveMessage));
_this.clear_all_notifications();
at_least_one_text_input_found = false _this.saveNotification.show();
one_text_input_filled = false return _this.focus_on_save_notification();
@el.find("input:text").each (i, text_field) => } else {
if $(text_field).is(':visible') return _this.gentle_alert(saveMessage);
at_least_one_text_input_found = true }
if $(text_field).val() isnt '' });
one_text_input_filled = true };
if bind
$(text_field).on 'input', (e) => Problem.prototype.refreshMath = function (event, element) {
@saveNotification.hide() var elid, eqn, jax, mathjax_preprocessor, preprocessor_tag, target;
@submitAnswersAndSubmitButton() if (!element) {
return element = event.target;
return }
if at_least_one_text_input_found and not one_text_input_filled elid = element.id.replace(/^input_/, '');
answered = false target = "display_" + elid;
preprocessor_tag = "inputtype_" + elid;
@el.find(".choicegroup").each (i, choicegroup_block) => mathjax_preprocessor = this.inputtypeDisplays[preprocessor_tag];
checked = false if ((typeof MathJax !== "undefined" && MathJax !== null) && (jax = MathJax.Hub.getAllJax(target)[0])) {
$(choicegroup_block).find("input[type=checkbox], input[type=radio]").each (j, checkbox_or_radio) => eqn = $(element).val();
if $(checkbox_or_radio).is(':checked') if (mathjax_preprocessor) {
checked = true eqn = mathjax_preprocessor(eqn);
if bind }
$(checkbox_or_radio).on 'click', (e) => MathJax.Hub.Queue(['Text', jax, eqn], [this.updateMathML, jax, element]);
@saveNotification.hide() }
@submitAnswersAndSubmitButton() };
return
return Problem.prototype.updateMathML = function (jax, element) {
if not checked try {
answered = false return $("#" + element.id + "_dynamath").val(jax.root.toMathML(''));
return } catch (exception) {
if (!exception.restart) {
@el.find("select").each (i, select_field) => throw exception;
selected_option = $(select_field).find("option:selected").text().trim() }
if selected_option is 'Select an option' if (typeof MathJax !== "undefined" && MathJax !== null) {
answered = false return MathJax.Callback.After([this.refreshMath, jax], exception.restart);
if bind }
$(select_field).on 'change', (e) => }
@saveNotification.hide() };
@submitAnswersAndSubmitButton()
return Problem.prototype.refreshAnswers = function () {
return this.$('input.schematic').each(function (index, element) {
return element.schematic.update_value();
if answered });
@enableSubmitButton true this.$(".CodeMirror").each(function (index, element) {
else if (element.CodeMirror.save) {
@enableSubmitButton false, false return element.CodeMirror.save();
}
bindResetCorrectness: -> });
# Loop through all input types return this.answers = this.inputs.serialize();
# Bind the reset functions at that scope. };
$inputtypes = @el.find(".capa_inputtype").add(@el.find(".inputtype"))
$inputtypes.each (index, inputtype) => Problem.prototype.submitAnswersAndSubmitButton = function (bind) {
classes = $(inputtype).attr('class').split(' ') var answered, at_least_one_text_input_found, one_text_input_filled,
for cls in classes _this = this;
bindMethod = @bindResetCorrectnessByInputtype[cls] if (bind == null) {
if bindMethod? bind = false;
bindMethod(inputtype) }
"Used to check available answers and if something is checked (or the answer is set in some textbox)\n\"Submit\" button becomes enabled. Otherwise it is disabled by default.\n\nArguments:\n bind (bool): used on the first check to attach event handlers to input fields\n to change \"Submit\" enable status in case of some manipulations with answers";
# Find all places where each input type displays its correct-ness answered = true;
# Replace them with their original state--'unanswered'. at_least_one_text_input_found = false;
bindResetCorrectnessByInputtype: one_text_input_filled = false;
# These are run at the scope of the capa inputtype this.el.find("input:text").each(function (i, text_field) {
# They should set handlers on each <input> to reset the whole. if ($(text_field).is(':visible')) {
formulaequationinput: (element) -> at_least_one_text_input_found = true;
$(element).find('input').on 'input', -> if ($(text_field).val() !== '') {
$p = $(element).find('span.status') one_text_input_filled = true;
`// Translators: the word unanswered here is about answering a problem the student must solve.` }
$p.parent().removeClass().addClass "unsubmitted" if (bind) {
$(text_field).on('input', function (e) {
choicegroup: (element) -> _this.saveNotification.hide();
$element = $(element) _this.submitAnswersAndSubmitButton();
id = ($element.attr('id').match /^inputtype_(.*)$/)[1] });
$element.find('input').on 'change', -> }
$status = $("#status_#{id}") }
if $status[0] # We found a status icon. });
$status.removeClass().addClass "unanswered" if (at_least_one_text_input_found && !one_text_input_filled) {
$status.empty().css 'display', 'inline-block' answered = false;
else }
# Recreate the unanswered dot on left. this.el.find(".choicegroup").each(function (i, choicegroup_block) {
$("<span>", {"class": "unanswered", "style": "display: inline-block;", "id": "status_#{id}"}) var checked;
checked = false;
$element.find("label").removeClass() $(choicegroup_block).find("input[type=checkbox], input[type=radio]").each(function (j, checkbox_or_radio) {
if ($(checkbox_or_radio).is(':checked')) {
'option-input': (element) -> checked = true;
$select = $(element).find('select') }
id = ($select.attr('id').match /^input_(.*)$/)[1] if (bind) {
$select.on 'change', -> $(checkbox_or_radio).on('click', function (e) {
$status = $("#status_#{id}") _this.saveNotification.hide();
.removeClass().addClass("unanswered") _this.submitAnswersAndSubmitButton();
.find('span').text(gettext('Status: unsubmitted')) });
}
textline: (element) -> });
$(element).find('input').on 'input', -> if (!checked) {
$p = $(element).find('span.status') answered = false;
`// Translators: the word unanswered here is about answering a problem the student must solve.` }
$p.parent().removeClass("correct incorrect").addClass "unsubmitted" });
this.el.find("select").each(function (i, select_field) {
inputtypeSetupMethods: var selected_option;
selected_option = $(select_field).find("option:selected").text().trim();
'text-input-dynamath': (element) => if (selected_option === 'Select an option') {
### answered = false;
}
if (bind) {
$(select_field).on('change', function (e) {
_this.saveNotification.hide();
_this.submitAnswersAndSubmitButton();
});
}
});
if (answered) {
return this.enableSubmitButton(true);
} else {
return this.enableSubmitButton(false, false);
}
};
Problem.prototype.bindResetCorrectness = function () {
var $inputtypes,
_this = 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 = _this.bindResetCorrectnessByInputtype[cls];
if (bindMethod != null) {
_results.push(bindMethod(inputtype));
} else {
_results.push(void 0);
}
}
return _results;
});
};
Problem.prototype.bindResetCorrectnessByInputtype = {
formulaequationinput: function (element) {
return $(element).find('input').on('input', function () {
var $p;
$p = $(element).find('span.status');
// Translators: the word unanswered here is about answering a problem the student must solve.;
return $p.parent().removeClass().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.removeClass().addClass("unanswered");
$status.empty().css('display', 'inline-block');
} else {
$("<span>", {
"class": "unanswered",
"style": "display: inline-block;",
"id": "status_" + id
});
}
return $element.find("label").removeClass();
});
},
'option-input': function (element) {
var $select, id;
$select = $(element).find('select');
id = ($select.attr('id').match(/^input_(.*)$/))[1];
return $select.on('change', function () {
var $status;
return $status = $("#status_" + id).removeClass().addClass("unanswered").find('span').text(gettext('Status: unsubmitted'));
});
},
textline: function (element) {
return $(element).find('input').on('input', function () {
var $p;
$p = $(element).find('span.status');
// Translators: the word unanswered here is about answering a problem the student must solve.;
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 Return: function (eqn) -> eqn that preprocesses the user formula input before
it is fed into MathJax. Return 'false' if no preprocessor specified it is fed into MathJax. Return 'false' if no preprocessor specified
### */
data = $(element).find('.text-input-dynamath_data')
var data, preprocessor, preprocessorClass, preprocessorClassName;
preprocessorClassName = data.data('preprocessor') data = $(element).find('.text-input-dynamath_data');
preprocessorClass = window[preprocessorClassName] preprocessorClassName = data.data('preprocessor');
if not preprocessorClass? preprocessorClass = window[preprocessorClassName];
return false if (preprocessorClass == null) {
else return false;
preprocessor = new preprocessorClass() } else {
return preprocessor.fn preprocessor = new preprocessorClass();
return preprocessor.fn;
javascriptinput: (element) => }
},
data = $(element).find(".javascriptinput_data") javascriptinput: function (element) {
var container, data, display, displayClass, evaluation, params, problemState, submission, submissionField;
params = data.data("params") data = $(element).find(".javascriptinput_data");
submission = data.data("submission") params = data.data("params");
evaluation = data.data("evaluation") submission = data.data("submission");
problemState = data.data("problem_state") evaluation = data.data("evaluation");
displayClass = window[data.data('display_class')] problemState = data.data("problem_state");
displayClass = window[data.data('display_class')];
if evaluation == '' if (evaluation === '') {
evaluation = null evaluation = null;
}
container = $(element).find(".javascriptinput_container") container = $(element).find(".javascriptinput_container");
submissionField = $(element).find(".javascriptinput_input") submissionField = $(element).find(".javascriptinput_input");
display = new displayClass(problemState, submission, evaluation, container, submissionField, params);
display = new displayClass(problemState, submission, evaluation, container, submissionField, params) display.render();
display.render() return display;
},
return display cminput: function (container) {
var CodeMirrorEditor, CodeMirrorTextArea, element, id, linenumbers, mode, spaces, tabsize;
cminput: (container) => element = $(container).find("textarea");
element = $(container).find("textarea") tabsize = element.data("tabsize");
tabsize = element.data("tabsize") mode = element.data("mode");
mode = element.data("mode") linenumbers = element.data("linenums");
linenumbers = element.data("linenums") spaces = Array(parseInt(tabsize) + 1).join(" ");
spaces = Array(parseInt(tabsize) + 1).join(" ") CodeMirrorEditor = CodeMirror.fromTextArea(element[0], {
CodeMirrorEditor = CodeMirror.fromTextArea element[0], { lineNumbers: linenumbers,
lineNumbers: linenumbers indentUnit: tabsize,
indentUnit: tabsize tabSize: tabsize,
tabSize: tabsize mode: mode,
mode: mode matchBrackets: true,
matchBrackets: true lineWrapping: true,
lineWrapping: true indentWithTabs: false,
indentWithTabs: false smartIndent: false,
smartIndent: false
extraKeys: { extraKeys: {
"Esc": (cm) -> "Esc": function (cm) {
$(".grader-status").focus() $(".grader-status").focus();
return false return false;
"Tab": (cm) -> },
cm.replaceSelection(spaces, "end") "Tab": function (cm) {
return false cm.replaceSelection(spaces, "end");
} return false;
} }
id = element.attr("id").replace(/^input_/, "") }
CodeMirrorTextArea = CodeMirrorEditor.getInputField() });
CodeMirrorTextArea.setAttribute("id", "cm-textarea-#{id}") id = element.attr("id").replace(/^input_/, "");
CodeMirrorTextArea.setAttribute("aria-describedby", "cm-editor-exit-message-#{id} status_#{id}") CodeMirrorTextArea = CodeMirrorEditor.getInputField();
return CodeMirrorEditor CodeMirrorTextArea.setAttribute("id", "cm-textarea-" + id);
CodeMirrorTextArea.setAttribute("aria-describedby", "cm-editor-exit-message-" + id + " status_" + id);
inputtypeShowAnswerMethods: return CodeMirrorEditor;
choicegroup: (element, display, answers) => }
element = $(element) };
input_id = element.attr('id').replace(/inputtype_/, '') Problem.prototype.inputtypeShowAnswerMethods = {
answer = answers[input_id] choicegroup: function (element, display, answers) {
for choice in answer var answer, choice, input_id, _i, _len, _results;
element.find("#input_#{input_id}_#{choice}").parent("label").addClass 'choicegroup_correct' element = $(element);
input_id = element.attr('id').replace(/inputtype_/, '');
javascriptinput: (element, display, answers) => answer = answers[input_id];
answer_id = $(element).attr('id').split("_")[1...].join("_") _results = [];
answer = JSON.parse(answers[answer_id]) for (_i = 0, _len = answer.length; _i < _len; _i++) {
display.showAnswer(answer) choice = answer[_i];
_results.push(element.find("#input_" + input_id + "_" + choice).parent("label").addClass('choicegroup_correct'));
choicetextgroup: (element, display, answers) => }
element = $(element) return _results;
},
input_id = element.attr('id').replace(/inputtype_/, '') javascriptinput: function (element, display, answers) {
answer = answers[input_id] var answer, answer_id;
for choice in answer answer_id = $(element).attr('id').split("_").slice(1).join("_");
element.find("section#forinput#{choice}").addClass 'choicetextgroup_show_correct' answer = JSON.parse(answers[answer_id]);
return display.showAnswer(answer);
imageinput: (element, display, answers) => },
# answers is a dict of (answer_id, answer_text) for each answer for this choicetextgroup: function (element, display, answers) {
# question. var answer, choice, input_id, _i, _len, _results;
# @Examples: element = $(element);
# {'anwser_id': { input_id = element.attr('id').replace(/inputtype_/, '');
# 'rectangle': '(10,10)-(20,30);(12,12)-(40,60)', answer = answers[input_id];
# 'regions': '[[10,10], [30,30], [10, 30], [30, 10]]' _results = [];
# } } for (_i = 0, _len = answer.length; _i < _len; _i++) {
types = choice = answer[_i];
rectangle: (ctx, coords) => _results.push(element.find("section#forinput" + choice).addClass('choicetextgroup_show_correct'));
reg = /^\(([0-9]+),([0-9]+)\)-\(([0-9]+),([0-9]+)\)$/ }
rects = coords.replace(/\s*/g, '').split(/;/) return _results;
},
$.each rects, (index, rect) => imageinput: function (element, display, answers) {
abs = Math.abs var canvas, container, ctx, id, types;
points = reg.exec(rect) types = {
if points rectangle: function (ctx, coords) {
width = abs(points[3] - points[1]) var rects, reg;
height = abs(points[4] - points[2]) reg = /^\(([0-9]+),([0-9]+)\)-\(([0-9]+),([0-9]+)\)$/;
rects = coords.replace(/\s*/g, '').split(/;/);
ctx.rect(points[1], points[2], width, height) $.each(rects, function (index, rect) {
var abs, height, points, width;
ctx.stroke() abs = Math.abs;
ctx.fill() points = reg.exec(rect);
if (points) {
regions: (ctx, coords) => width = abs(points[3] - points[1]);
parseCoords = (coords) => height = abs(points[4] - points[2]);
reg = JSON.parse(coords) return ctx.rect(points[1], points[2], width, height);
}
# Regions is list of lists [region1, region2, region3, ...] where regionN });
# is disordered list of points: [[1,1], [100,100], [50,50], [20, 70]]. ctx.stroke();
# If there is only one region in the list, simpler notation can be used: return ctx.fill();
# regions="[[10,10], [30,30], [10, 30], [30, 10]]" (without explicitly },
# setting outer list) regions: function (ctx, coords) {
if typeof reg[0][0][0] == "undefined" var parseCoords;
# we have [[1,2],[3,4],[5,6]] - single region parseCoords = function (coords) {
# instead of [[[1,2],[3,4],[5,6], [[1,2],[3,4],[5,6]]] var reg;
# or [[[1,2],[3,4],[5,6]]] - multiple regions syntax reg = JSON.parse(coords);
reg = [reg] if (typeof reg[0][0][0] === "undefined") {
reg = [reg];
return reg }
return reg;
$.each parseCoords(coords), (index, region) => };
ctx.beginPath() return $.each(parseCoords(coords), function (index, region) {
$.each region, (index, point) => ctx.beginPath();
if index is 0 $.each(region, function (index, point) {
ctx.moveTo(point[0], point[1]) if (index === 0) {
else return ctx.moveTo(point[0], point[1]);
ctx.lineTo(point[0], point[1]); } else {
return ctx.lineTo(point[0], point[1]);
ctx.closePath() }
ctx.stroke() });
ctx.fill() ctx.closePath();
ctx.stroke();
element = $(element) return ctx.fill();
id = element.attr('id').replace(/inputtype_/,'') });
container = element.find("#answer_#{id}") }
canvas = document.createElement('canvas') };
canvas.width = container.data('width') element = $(element);
canvas.height = container.data('height') id = element.attr('id').replace(/inputtype_/, '');
container = element.find("#answer_" + id);
if canvas.getContext canvas = document.createElement('canvas');
ctx = canvas.getContext('2d') canvas.width = container.data('width');
else canvas.height = container.data('height');
return console.log 'Canvas is not supported.' if (canvas.getContext) {
ctx = canvas.getContext('2d');
} else {
return console.log('Canvas is not supported.');
}
ctx.fillStyle = 'rgba(255,255,255,.3)'; ctx.fillStyle = 'rgba(255,255,255,.3)';
ctx.strokeStyle = "#FF0000"; ctx.strokeStyle = "#FF0000";
ctx.lineWidth = "2"; ctx.lineWidth = "2";
if (answers[id]) {
$.each(answers[id], function (key, value) {
if ((types[key] != null) && value) {
return types[key](ctx, value);
}
});
return container.html(canvas);
} else {
return console.log("Answer is absent for image input with id=" + id);
}
}
};
Problem.prototype.inputtypeHideAnswerMethods = {
choicegroup: function (element, display) {
element = $(element);
return element.find('label').removeClass('choicegroup_correct');
},
javascriptinput: function (element, display) {
return display.hideAnswer();
},
choicetextgroup: function (element, display) {
element = $(element);
return element.find("section[id^='forinput']").removeClass('choicetextgroup_show_correct');
}
};
Problem.prototype.disableAllButtonsWhileRunning = function (operationCallback, isFromCheckOperation) {
var _this = this;
this.enableAllButtons(false, isFromCheckOperation);
return operationCallback().always(function () {
return _this.enableAllButtons(true, isFromCheckOperation);
});
};
Problem.prototype.enableAllButtons = function (enable, isFromCheckOperation) {
if (enable) {
this.resetButton.add(this.saveButton).add(this.hintButton).add(this.showButton).removeAttr('disabled');
} else {
this.resetButton.add(this.saveButton).add(this.hintButton).add(this.showButton).attr({
'disabled': 'disabled'
});
}
return this.enableSubmitButton(enable, isFromCheckOperation);
};
Problem.prototype.enableSubmitButton = function (enable, changeText) {
var submitCanBeEnabled;
if (changeText == null) {
changeText = true;
}
if (enable) {
submitCanBeEnabled = this.submitButton.data('should-enable-submit-button') === 'True';
if (submitCanBeEnabled) {
this.submitButton.removeAttr('disabled');
}
if (changeText) {
return this.submitButtonLabel.text(this.submitButtonSubmitText);
}
} else {
this.submitButton.attr({
'disabled': 'disabled'
});
if (changeText) {
return this.submitButtonLabel.text(this.submitButtonSubmittingText);
}
}
};
Problem.prototype.enableSubmitButtonAfterResponse = function () {
this.has_response = true;
if (!this.has_timed_out) {
return this.enableSubmitButton(false);
} else {
return this.enableSubmitButton(true);
}
};
Problem.prototype.enableSubmitButtonAfterTimeout = function () {
var enableSubmitButton,
_this = this;
this.has_timed_out = false;
this.has_response = false;
enableSubmitButton = function () {
_this.has_timed_out = true;
if (_this.has_response) {
return _this.enableSubmitButton(true);
}
};
return window.setTimeout(enableSubmitButton, 750);
};
Problem.prototype.hint_button = function () {
var hint_container, hint_index, next_index,
_this = this;
hint_container = this.$('.problem-hint');
hint_index = hint_container.attr('hint_index');
if (hint_index === void 0) {
next_index = 0;
} else {
next_index = parseInt(hint_index) + 1;
}
return $.postWithPrefix("" + this.url + "/hint_button", {
hint_index: next_index,
input_id: this.id
}, function (response) {
var hint_msg_container;
if (response.success) {
hint_msg_container = _this.$('.problem-hint .notification-message');
hint_container.attr('hint_index', response.hint_index);
edx.HtmlUtils.setHtml(hint_msg_container, edx.HtmlUtils.HTML(response.msg));
MathJax.Hub.Queue(['Typeset', MathJax.Hub, hint_container[0]]);
if (response.should_enable_next_hint) {
_this.hintButton.removeAttr('disabled');
} else {
_this.hintButton.attr({
'disabled': 'disabled'
});
}
_this.el.find('.notification-hint').show();
return _this.focus_on_hint_notification();
} else {
return _this.gentle_alert(response.msg);
}
});
};
return Problem;
}).call(this);
if answers[id] }).call(this);
$.each answers[id], (key, value) =>
types[key](ctx, value) if types[key]? and value
container.html(canvas)
else
console.log "Answer is absent for image input with id=#{id}"
inputtypeHideAnswerMethods:
choicegroup: (element, display) =>
element = $(element)
element.find('label').removeClass('choicegroup_correct')
javascriptinput: (element, display) =>
display.hideAnswer()
choicetextgroup: (element, display) =>
element = $(element)
element.find("section[id^='forinput']").removeClass('choicetextgroup_show_correct')
disableAllButtonsWhileRunning: (operationCallback, isFromCheckOperation) =>
# 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
# @submit, if so then text of submit button will be changed as well.
@enableAllButtons false, isFromCheckOperation
operationCallback().always =>
@enableAllButtons true, isFromCheckOperation
# Called by disableAllButtonsWhileRunning to automatically disable all buttons while check,reset, or
# save internal are running. Then enable all the buttons again after it is done.
enableAllButtons: (enable, isFromCheckOperation) =>
# Used to enable/disable all buttons in problem.
# params:
# 'enable' is a boolean to determine enabling/disabling of buttons.
# 'isFromCheckOperation' 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.
if enable
@resetButton
.add(@saveButton)
.add(@hintButton)
.add(@showButton)
.removeAttr 'disabled'
else
@resetButton
.add(@saveButton)
.add(@hintButton)
.add(@showButton)
.attr({'disabled': 'disabled'})
@enableSubmitButton enable, isFromCheckOperation
enableSubmitButton: (enable, changeText = true) =>
# 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.
if enable
submitCanBeEnabled = @submitButton.data('should-enable-submit-button') == 'True'
if submitCanBeEnabled
@submitButton.removeAttr 'disabled'
if changeText
@submitButtonLabel.text(@submitButtonSubmitText)
else
@submitButton.attr({'disabled': 'disabled'})
if changeText
@submitButtonLabel.text(@submitButtonSubmittingText)
enableSubmitButtonAfterResponse: =>
@has_response = true
if not @has_timed_out
# Server has returned response before our timeout
@enableSubmitButton false
else
@enableSubmitButton true
enableSubmitButtonAfterTimeout: =>
@has_timed_out = false
@has_response = false
enableSubmitButton = () =>
@has_timed_out = true
if @has_response
@enableSubmitButton true
window.setTimeout(enableSubmitButton, 750)
hint_button: =>
# Store the index of the currently shown hint as an attribute.
# Use that to compute the next hint number when the button is clicked.
hint_container = @.$('.problem-hint')
hint_index = hint_container.attr('hint_index')
if hint_index == undefined
next_index = 0
else
next_index = parseInt(hint_index) + 1
$.postWithPrefix "#{@url}/hint_button", hint_index: next_index, input_id: @id, (response) =>
if response.success
hint_msg_container = @.$('.problem-hint .notification-message')
hint_container.attr('hint_index', response.hint_index)
edx.HtmlUtils.setHtml(hint_msg_container, edx.HtmlUtils.HTML(response.msg))
# Update any Mathjax entries
MathJax.Hub.Queue [
'Typeset'
MathJax.Hub
hint_container[0]
]
# Enable/Disable the next hint button
if response.should_enable_next_hint
@hintButton.removeAttr 'disabled'
else
@hintButton.attr({'disabled': 'disabled'})
@el.find('.notification-hint').show()
@focus_on_hint_notification()
else
@gentle_alert response.msg
...@@ -216,7 +216,7 @@ class @Sequence ...@@ -216,7 +216,7 @@ class @Sequence
widget_placement: widget_placement widget_placement: widget_placement
# On Sequence change, destroy any existing polling thread # On Sequence change, destroy any existing polling thread
# for queued submissions, see ../capa/display.coffee # for queued submissions, see ../capa/display.js
if window.queuePollerID if window.queuePollerID
window.clearTimeout(window.queuePollerID) window.clearTimeout(window.queuePollerID)
delete window.queuePollerID delete window.queuePollerID
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment