@@ -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 {
0 commit comments