Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit 6876d12

Browse files
committed
Support attachments on shortanswer Qs
1 parent 2aae87b commit 6876d12

File tree

4 files changed

+86
-20
lines changed

4 files changed

+86
-20
lines changed

runestone/common/js/runestonebase.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,12 @@ export default class RunestoneBase {
295295
data = await response.json();
296296
data = data.detail;
297297
this.repopulateFromStorage(data);
298+
this.attempted = true;
299+
if (typeof(data.correct) !== "undefined") {
300+
this.correct = data.correct;
301+
} else {
302+
this.correct = null;
303+
}
298304
this.csresolver("server");
299305
} else {
300306
console.log(

runestone/mchoice/js/mchoice.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export default class MultipleChoice extends RunestoneBase {
5252
// https://docs.mathjax.org/en/latest/options/startup/startup.html
5353
// https://docs.mathjax.org/en/latest/web/configuration.html#startup-action
5454
// runestoneMathReady is defined in the preamble for all PTX authored books
55-
this.queueMathJax(self.containerDiv);
55+
this.queueMathJax(this.containerDiv);
5656
if (typeof Prism !== "undefined") {
5757
Prism.highlightAllUnder(this.containerDiv);
5858
}

runestone/shortanswer/js/shortanswer.js

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ export default class ShortAnswer extends RunestoneBase {
2828
this.divid = orig.id;
2929
this.question = this.origElem.innerHTML;
3030
this.optional = false;
31+
this.attachURL = opts.attachURL;
3132
if ($(this.origElem).is("[data-optional]")) {
3233
this.optional = true;
3334
}
3435
if ($(this.origElem).is("[data-mathjax]")) {
3536
this.mathjax = true;
3637
}
38+
if ($(this.origElem).is("[data-attachment]")) {
39+
this.attachment = true;
40+
}
3741
this.placeholder =
3842
$(this.origElem).data("placeholder") ||
3943
"Write your answer here";
@@ -73,7 +77,7 @@ export default class ShortAnswer extends RunestoneBase {
7377
this.jOptionsDiv.appendChild(this.jLabel);
7478
this.jTextArea = document.createElement("textarea");
7579
let self = this;
76-
this.jTextArea.onchange = function () {
80+
this.jTextArea.onchange = function() {
7781
self.isAnswered = true;
7882
};
7983
this.jTextArea.id = this.divid + "_solution";
@@ -84,7 +88,7 @@ export default class ShortAnswer extends RunestoneBase {
8488
this.jTextArea.rows = 4;
8589
this.jTextArea.cols = 50;
8690
this.jLabel.appendChild(this.jTextArea);
87-
this.jTextArea.onchange = function () {
91+
this.jTextArea.onchange = function() {
8892
this.feedbackDiv.innerHTML = "Your answer has not been saved yet!";
8993
$(this.feedbackDiv).removeClass("alert-success");
9094
$(this.feedbackDiv).addClass("alert alert-danger");
@@ -101,7 +105,7 @@ export default class ShortAnswer extends RunestoneBase {
101105
$(this.submitButton).addClass("btn btn-success");
102106
this.submitButton.type = "button";
103107
this.submitButton.textContent = "Save";
104-
this.submitButton.onclick = function () {
108+
this.submitButton.onclick = function() {
105109
this.checkCurrentAnswer();
106110
this.logCurrentAnswer();
107111
this.renderFeedback();
@@ -124,6 +128,24 @@ export default class ShortAnswer extends RunestoneBase {
124128
$(this.feedbackDiv).addClass("alert alert-danger");
125129
//this.otherOptionsDiv.appendChild(this.feedbackDiv);
126130
this.fieldSet.appendChild(this.feedbackDiv);
131+
if (this.attachment) {
132+
let attachDiv = document.createElement("div")
133+
if (this.graderactive ) {
134+
// If in grading mode make a button to create a popup with the image
135+
let viewButton = document.createElement("button")
136+
viewButton.type = "button"
137+
viewButton.innerHTML = "View Attachment"
138+
viewButton.onclick = this.viewFile.bind(this);
139+
attachDiv.appendChild(viewButton);
140+
} else {
141+
// Otherwise make a button for the student to select a file to upload.
142+
this.fileUpload = document.createElement("input")
143+
this.fileUpload.type = "file";
144+
this.fileUpload.id = `${this.divid}_fileme`;
145+
attachDiv.appendChild(this.fileUpload);
146+
}
147+
this.containerDiv.appendChild(attachDiv);
148+
}
127149
//this.fieldSet.appendChild(document.createElement("br"));
128150
$(this.origElem).replaceWith(this.containerDiv);
129151
// This is a stopgap measure for when MathJax is not loaded at all. There is another
@@ -163,6 +185,9 @@ export default class ShortAnswer extends RunestoneBase {
163185
data.sid = sid;
164186
}
165187
await this.logBookEvent(data);
188+
if (this.attachment) {
189+
await this.uploadFile();
190+
}
166191
}
167192

168193
renderFeedback() {
@@ -234,7 +259,7 @@ export default class ShortAnswer extends RunestoneBase {
234259
$(toggle_answer_button).css("margin-left", "5px");
235260

236261
$(toggle_answer_button).click(
237-
function () {
262+
function() {
238263
var display_timestamp, button_text;
239264
if (this.current_answer === "ontime") {
240265
this.jTextArea.value = data.last_answer;
@@ -275,14 +300,42 @@ export default class ShortAnswer extends RunestoneBase {
275300
disableInteraction() {
276301
this.jTextArea.disabled = true;
277302
}
303+
304+
async uploadFile() {
305+
const files = this.fileUpload.files
306+
const data = new FormData()
307+
if (this.fileUpload.files.length > 0) {
308+
data.append('file', files[0])
309+
fetch(`/ns/logger/upload/${this.divid}`, {
310+
method: 'POST',
311+
body: data
312+
})
313+
.then(response => response.json())
314+
.then(data => {
315+
console.log(data)
316+
})
317+
.catch(error => {
318+
console.error(error)
319+
})
320+
}
321+
}
322+
323+
viewFile() {
324+
// Get the URL from the S3 API -- saved when we display in grader mode
325+
if (this.attachURL) {
326+
window.open(this.attachURL, "_blank");
327+
} else {
328+
alert("No attachment for this student.")
329+
}
330+
}
278331
}
279332

280333
/*=================================
281334
== Find the custom HTML tags and ==
282335
== execute our code on them ==
283336
=================================*/
284-
$(document).on("runestone:login-complete", function () {
285-
$("[data-component=shortanswer]").each(function () {
337+
$(document).on("runestone:login-complete", function() {
338+
$("[data-component=shortanswer]").each(function() {
286339
if ($(this).closest("[data-component=timedAssessment]").length == 0) {
287340
// If this element exists within a timed component, don't render it here
288341
try {

runestone/shortanswer/shortanswer.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@
2929

3030
def setup(app):
3131
app.add_directive("shortanswer", JournalDirective)
32-
app.add_node(JournalNode, html=(visit_journal_html, depart_journal_html),
33-
xml=(visit_journal_xml, depart_journal_xml))
32+
app.add_node(
33+
JournalNode,
34+
html=(visit_journal_html, depart_journal_html),
35+
xml=(visit_journal_xml, depart_journal_xml),
36+
)
3437

3538
app.add_config_value("shortanswer_div_class", "journal", "html")
3639
app.add_config_value(
@@ -40,7 +43,7 @@ def setup(app):
4043

4144
TEXT_START = """
4245
<div class="runestone">
43-
<div data-component="shortanswer" data-question_label="%(question_label)s" class="%(divclass)s" id=%(divid)s %(optional)s %(mathjax)s>
46+
<div data-component="shortanswer" data-question_label="%(question_label)s" class="%(divclass)s" id=%(divid)s %(optional)s %(mathjax)s %(attachment)s >
4447
"""
4548

4649
TEXT_END = """
@@ -55,7 +58,7 @@ def setup(app):
5558

5659
XML_END = """
5760
</statement>
58-
</exercise>
61+
</exercise>
5962
"""
6063

6164

@@ -102,23 +105,23 @@ def depart_journal_xml(self, node):
102105

103106
class JournalDirective(Assessment):
104107
"""
105-
.. shortanswer:: uniqueid
106-
:optional:
108+
.. shortanswer:: uniqueid
109+
:optional:
107110
108-
text of the question goes here
111+
text of the question goes here
109112
110113
111-
config values (conf.py):
114+
config values (conf.py):
112115
113-
- shortanswer_div_class - custom CSS class of the component's outermost div
116+
- shortanswer_div_class - custom CSS class of the component's outermost div
114117
"""
115118

116119
required_arguments = 1 # the div id
117120
optional_arguments = 0
118121
final_argument_whitespace = True
119122
has_content = True
120123
option_spec = Assessment.option_spec.copy()
121-
option_spec.update({"mathjax": directives.flag})
124+
option_spec.update({"mathjax": directives.flag, "attachment": directives.flag})
122125

123126
node_class = JournalNode
124127

@@ -129,12 +132,16 @@ def run(self):
129132
self.assert_has_content()
130133

131134
self.options["mathjax"] = "data-mathjax" if "mathjax" in self.options else ""
135+
self.options["attachment"] = (
136+
"data-attachment" if "attachment" in self.options else ""
137+
)
132138

133139
journal_node = JournalNode()
134140
journal_node["runestone_options"] = self.options
135-
journal_node["source"], journal_node["line"] = self.state_machine.get_source_and_line(
136-
self.lineno
137-
)
141+
(
142+
journal_node["source"],
143+
journal_node["line"],
144+
) = self.state_machine.get_source_and_line(self.lineno)
138145

139146
self.updateContent()
140147

0 commit comments

Comments
 (0)