1010//Styling:
1111// TODO: Review all styling in all scenarios (staged/not, correct/partly-correct/incorrect/blank, single/multiple)
1212
13- function handleWW ( ww_id , action ) {
13+ async function handleWW ( ww_id , action ) {
1414 const ww_container = document . getElementById ( ww_id ) ;
1515 const ww_domain = ww_container . dataset . domain ;
16+ const ww_renderer = ww_container . dataset . renderer ;
17+ const ww_processing = ww_container . dataset . processing ;
18+ const ww_origin = ww_container . dataset . origin ;
1619 const ww_problemSource = ww_container . dataset . problemsource ;
1720 const ww_sourceFilePath = ww_container . dataset . sourcefilepath ;
1821 const ww_course_id = ww_container . dataset . courseid ;
1922 const ww_user_id = ww_container . dataset . userid ;
20- const ww_course_password = ww_container . dataset . coursepassword ;
21- const localize_correct = ww_container . dataset . localizeCorrect || "Correct" ;
22- const localize_incorrect = ww_container . dataset . localizeIncorrect || "Incorrect" ;
23- const localize_blank = ww_container . dataset . localizeBlank || "Blank" ;
23+ const ww_passwd = ww_container . dataset . passwd ;
2424 const localize_submit = ww_container . dataset . localizeSubmit || "Submit" ;
2525 const localize_check_responses = ww_container . dataset . localizeCheckResponses || "Check Responses" ;
2626 const localize_reveal = ww_container . dataset . localizeReveal || "Reveal" ;
@@ -74,34 +74,81 @@ function handleWW(ww_id, action) {
7474 }
7575
7676 let url ;
77+ if ( ww_processing == 'webwork2' ) {
78+ url = new URL ( ww_domain + '/webwork2/render_rpc' ) ;
79+ } else if ( ww_processing == 'renderer' ) {
80+ url = new URL ( ww_renderer + '/renderer/render-api' ) ;
81+ }
82+ let formData = new FormData ( ) ;
7783
78- if ( action == 'check' ) {
84+ if ( action == 'check' || action == 'reveal' ) {
7985 const iframe = ww_container . querySelector ( '.problem-iframe' ) ;
80- const formData = new FormData ( iframe . contentDocument . getElementById ( ww_id + "-form" ) ) ;
81- const params = new URLSearchParams ( formData ) ;
82- url = new URL ( ww_domain + '/webwork2/render_rpc?' + params . toString ( ) )
83- url . searchParams . append ( "answersSubmitted" , '1' ) ;
84- url . searchParams . append ( 'WWsubmit' , "1" ) ;
86+ formData = new FormData ( iframe . contentDocument . getElementById ( ww_id + "-form" ) ) ;
87+ formData . set ( "answersSubmitted" , '1' ) ;
88+ formData . set ( 'WWsubmit' , "1" ) ;
89+ if ( action == 'reveal' && ww_container . dataset . hasAnswer == 'true' ) {
90+ formData . set ( 'WWcorrectAnsOnly' , "1" ) ;
91+ }
92+ if ( ww_origin == 'generated' ) {
93+ const rawProblemSource = await fetch ( ww_problemSource ) . then ( ( r ) => r . text ( ) ) ;
94+ formData . set ( "rawProblemSource" , rawProblemSource ) ;
95+ }
96+ else if ( ww_origin == 'external' ) {
97+ const rawProblemSource = await fetch ( ww_sourceFilePath ) . then ( ( r ) => r . text ( ) ) ;
98+ formData . set ( "rawProblemSource" , rawProblemSource ) ;
99+ }
100+ else if ( ww_origin == 'webwork2' ) formData . set ( "sourceFilePath" , ww_sourceFilePath ) ;
85101 } else {
86- url = new URL ( ww_domain + '/webwork2/render_rpc' ) ;
87- url . searchParams . append ( "problemSeed" , ww_container . dataset . current_seed ) ;
88- if ( ww_problemSource ) url . searchParams . append ( "problemSource" , ww_problemSource ) ;
89- else if ( ww_sourceFilePath ) url . searchParams . append ( "sourceFilePath" , ww_sourceFilePath ) ;
90- url . searchParams . append ( "answersSubmitted" , '0' ) ;
91- url . searchParams . append ( "displayMode" , "MathJax" ) ;
92- url . searchParams . append ( "courseID" , ww_course_id ) ;
93- url . searchParams . append ( "user" , ww_user_id ) ;
94- url . searchParams . append ( "passwd" , ww_course_password ) ;
95- url . searchParams . append ( "disableCookes" , '1' ) ;
96- url . searchParams . append ( "outputformat" , "raw" ) ;
102+ formData . set ( "problemSeed" , ww_container . dataset . current_seed ) ;
103+ if ( ww_origin == 'generated' ) {
104+ const rawProblemSource = await fetch ( ww_problemSource ) . then ( ( r ) => r . text ( ) ) ;
105+ formData . set ( "rawProblemSource" , rawProblemSource ) ;
106+ }
107+ else if ( ww_origin == 'external' ) {
108+ const rawProblemSource = await fetch ( ww_sourceFilePath ) . then ( ( r ) => r . text ( ) ) ;
109+ formData . set ( "rawProblemSource" , rawProblemSource ) ;
110+ }
111+ else if ( ww_origin == 'webwork2' ) formData . set ( "sourceFilePath" , ww_sourceFilePath ) ;
112+ formData . set ( "answersSubmitted" , '0' ) ;
113+ formData . set ( "displayMode" , "MathJax" ) ;
114+ formData . set ( "courseID" , ww_course_id ) ;
115+ formData . set ( "user" , ww_user_id ) ;
116+ formData . set ( "userID" , ww_user_id ) ;
117+ formData . set ( "passwd" , ww_passwd ) ;
118+ formData . set ( "disableCookies" , '1' ) ;
119+ formData . set ( "outputformat" , "raw" ) ;
97120 // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true
98- url . searchParams . append ( "showSolutions" , ww_container . dataset . hasSolution == 'true' ? '1' : '0' ) ;
99- url . searchParams . append ( "showHints" , ww_container . dataset . hasHint == 'true' ? '1' : '0' ) ;
100- url . searchParams . append ( "problemUUID" , ww_id ) ;
121+ formData . set ( "showSolutions" , ww_container . dataset . hasSolution == 'true' ? '1' : '0' ) ;
122+ formData . set ( "showHints" , ww_container . dataset . hasHint == 'true' ? '1' : '0' ) ;
123+ formData . set ( "problemUUID" , ww_id ) ;
101124 }
102125
103- // get the json and do stuff with what we get
104- $ . getJSON ( url . toString ( ) , ( data ) => {
126+ // If in Runestone, check if there are previous answer submissions and get them now to use in the form.
127+ let checkboxesString = '' ;
128+ if ( runestone_logged_in && ! action ) {
129+ const answersObject = ( wwList [ ww_id . replace ( / - w w - r s $ / , '' ) ] . answers ? wwList [ ww_id . replace ( / - w w - r s $ / , '' ) ] . answers : { 'answers' : [ ] , 'mqAnswers' : [ ] } ) ;
130+ const previousAnswers = answersObject . answers ;
131+ if ( previousAnswers !== null ) {
132+ formData . set ( 'WWsubmit' , 1 ) ;
133+ }
134+ for ( const answer in previousAnswers ) {
135+ if ( previousAnswers [ answer ] . constructor === Array ) {
136+ for ( const k in previousAnswers [ answer ] ) {
137+ checkboxesString += '&' ;
138+ checkboxesString += answer ;
139+ checkboxesString += '=' ;
140+ checkboxesString += previousAnswers [ answer ] [ k ] ;
141+ }
142+ } else {
143+ formData . set ( answer , previousAnswers [ answer ] ) ;
144+ }
145+ }
146+ }
147+ // Need to get form data as a string, including possible repeated checkbox names
148+ // Do not pass post data as an object, or checkbox names will overwrite one another
149+ const formString = new URLSearchParams ( formData ) . toString ( ) ;
150+
151+ $ . post ( url , formString + checkboxesString , ( data ) => {
105152 // Create the form that will contain the text and input fields of the interactive problem.
106153 const form = document . createElement ( "form" ) ;
107154 form . id = ww_id + "-form" ;
@@ -117,6 +164,29 @@ function handleWW(ww_id, action) {
117164 // Dump the problem text, answer blanks, etc.
118165 body_div . innerHTML = data . rh_result . text ;
119166
167+ // If showPartialCorrectAnswers = 0, alter the feedback buttons according to whether or not the score is 100%.
168+ if ( 'showPartialCorrectAnswers' in data . rh_result . flags && data . rh_result . flags . showPartialCorrectAnswers == 0 ) {
169+ if ( 'score' in data . rh_result . problem_result && data . rh_result . problem_result . score >= 1 ) {
170+ body_div . querySelectorAll ( 'button.ww-feedback-btn' ) . forEach (
171+ function ( button ) {
172+ button . classList . remove ( 'btn-info' ) ;
173+ button . classList . add ( 'btn-success' ) ;
174+ button . setAttribute ( 'aria-label' , 'Correct' ) ;
175+ button . dataset . bsCustomClass = button . dataset . bsCustomClass + ' correct' ;
176+ button . dataset . bsTitle = button . dataset . bsTitle . replace ( 'Answer Preview' , 'Correct' ) ;
177+ button . firstChild . classList . add ( 'correct' )
178+ }
179+ ) ;
180+ } else {
181+ body_div . querySelectorAll ( 'button.ww-feedback-btn' ) . forEach (
182+ function ( button ) {
183+ button . setAttribute ( 'aria-label' , 'One or more answers incorrect' ) ;
184+ button . dataset . bsTitle = button . dataset . bsTitle . replace ( 'Answer Preview' , 'One or more answers incorrect' ) ;
185+ }
186+ ) ;
187+ }
188+ }
189+
120190 // Replace all hn headings with h6 headings.
121191 for ( const tag_name of [ 'h6' , 'h5' , 'h4' , 'h3' , 'h2' , 'h1' ] ) {
122192 const headings = body_div . getElementsByTagName ( tag_name ) ;
@@ -129,6 +199,19 @@ function handleWW(ww_id, action) {
129199 }
130200 }
131201
202+ // Hide textarea input and hide associated buttons
203+ var textareas = body_div . getElementsByTagName ( "textarea" ) ;
204+ for ( var i = 0 , max = textareas . length ; i < max ; i ++ )
205+ {
206+ textareas [ i ] . style . display = "none" ;
207+ textareas [ i ] . className = '' ;
208+ }
209+ var textareabuttons = body_div . querySelectorAll ( ".latexentry-preview" ) ;
210+ for ( var i = 0 , max = textareabuttons . length ; i < max ; i ++ )
211+ {
212+ textareabuttons [ i ] . remove ( ) ;
213+ }
214+
132215 adjustSrcHrefs ( body_div , ww_domain ) ;
133216
134217 translateHintSol ( ww_id , body_div , ww_domain ,
@@ -151,36 +234,6 @@ function handleWW(ww_id, action) {
151234 if ( input && input . value == '' ) {
152235 input . setAttribute ( 'value' , answers [ answer ] ) ;
153236 }
154- if ( input && input . type . toUpperCase ( ) == 'RADIO' ) {
155- const buttons = body_div . querySelectorAll ( 'input[name=' + answer + ']' ) ;
156- for ( const button of buttons ) {
157- if ( button . value == answers [ answer ] ) {
158- button . setAttribute ( 'checked' , 'checked' ) ;
159- }
160- }
161- }
162- if ( input && input . type . toUpperCase ( ) == 'CHECKBOX' ) {
163- const checkboxes = body_div . querySelectorAll ( 'input[name=' + answer + ']' ) ;
164- for ( const checkbox of checkboxes ) {
165- // This is not a bulletproof approach if the problem used input values that are weird
166- // For example, with commas in them
167- // However, we are stuck with WW providing answers[answer] as a string like `[value0, value1]`
168- // and note that it is not `["value0", "value1"]`, so we cannot cleanly parse it into an array
169- let checkbox_regex = new RegExp ( '(\\[|, )' + checkbox . value + '(, |\\])' ) ;
170- if ( answers [ answer ] . match ( checkbox_regex ) ) {
171- checkbox . setAttribute ( 'checked' , 'checked' ) ;
172- }
173- }
174- }
175- var select = body_div . querySelector ( 'select[id=' + answer + ']' ) ;
176- if ( select && answers [ answer ] ) {
177- // answers[answer] may be wrapped in \text{...} that we want to remove, since value does not have this.
178- let this_answer = answers [ answer ] ;
179- if ( / ^ \\ t e x t \{ .* \} $ / . test ( this_answer ) ) { this_answer = this_answer . match ( / ^ \\ t e x t \{ ( .* ) \} $ / ) [ 1 ] } ;
180- let quote_escaped_answer = this_answer . replace ( / " / g, '\\"' ) ;
181- const option = body_div . querySelector ( `select[id="${ answer } "] option[value="${ quote_escaped_answer } "]` ) ;
182- if ( option ) { option . setAttribute ( 'selected' , 'selected' ) } ;
183- }
184237 }
185238 }
186239
@@ -195,7 +248,7 @@ function handleWW(ww_id, action) {
195248 courseName : ww_course_id ,
196249 courseID : ww_course_id ,
197250 user : ww_user_id ,
198- passwd : ww_course_password ,
251+ passwd : ww_passwd ,
199252 displayMode : "MathJax" ,
200253 session_key : data . rh_result . session_key ,
201254 outputformat : "raw" ,
@@ -209,7 +262,7 @@ function handleWW(ww_id, action) {
209262 } ;
210263
211264 if ( ww_sourceFilePath ) wwInputs . sourceFilePath = ww_sourceFilePath ;
212- else if ( ww_problemSource ) wwInputs . problemSource = ww_problemSource ;
265+ else if ( ww_problemSource && ww_origin == 'webwork2' ) wwInputs . problemSource = ww_problemSource ;
213266
214267 for ( const wwInputName of Object . keys ( wwInputs ) ) {
215268 const input = document . createElement ( 'input' ) ;
@@ -219,31 +272,11 @@ function handleWW(ww_id, action) {
219272 form . appendChild ( input ) ;
220273 }
221274
222- // Prepare answers object
223- const answers = { } ;
224- // id the answers even if we won't populate them
225- Object . keys ( data . rh_result . answers ) . forEach ( function ( id ) {
226- answers [ id ] = { } ;
227- } , data . rh_result . answers ) ;
228- if ( ww_container . dataset . hasAnswer == 'true' ) {
229- // Update answer data
230- Object . keys ( data . rh_result . answers ) . forEach ( function ( id ) {
231- answers [ id ] = {
232- correct_ans : this [ id ] . correct_ans ,
233- correct_ans_latex_string : this [ id ] . correct_ans_latex_string ,
234- correct_choice : this [ id ] . correct_choice ,
235- correct_choices : this [ id ] . correct_choices ,
236- } ;
237- } , data . rh_result . answers ) ;
238- }
239-
240275 let buttonContainer = ww_container . querySelector ( '.problem-buttons.webwork' ) ;
241276 // Create the submission buttons if they have not yet been created.
242277 if ( ! buttonContainer ) {
243- // Hide the original div that contains the old make active button.
244- ww_container . querySelector ( '.problem-buttons' ) . classList . add ( 'hidden-content' ) ;
245- // And the newer activate button if it is there
246- if ( activate_button != null ) { activate_button . classList . add ( 'hidden-content' ) ; } ;
278+ // Hide the original div that contains the Activate.
279+ ww_container . querySelector ( '.problem-buttons' ) . classList . add ( 'hidden-content' , 'hidden' ) ;
247280
248281 // Create a new div for the webwork buttons.
249282 buttonContainer = document . createElement ( 'div' ) ;
@@ -262,11 +295,8 @@ function handleWW(ww_id, action) {
262295 check . style . marginRight = "0.25rem" ;
263296 check . classList . add ( 'webwork-button' ) ;
264297
265- // Adjust if more than one answer to check
266- const answerCount = body_div . querySelectorAll ( "input:not([type=hidden])" ) . length +
267- body_div . querySelectorAll ( "select:not([type=hidden])" ) . length ;
268-
269298 check . textContent = runestone_logged_in ? localize_submit : localize_check_responses ;
299+
270300 check . addEventListener ( 'click' , ( ) => handleWW ( ww_id , "check" ) ) ;
271301
272302 buttonContainer . appendChild ( check ) ;
@@ -278,7 +308,7 @@ function handleWW(ww_id, action) {
278308 correct . type = "button" ;
279309 correct . style . marginRight = "0.25rem" ;
280310 correct . textContent = localize_reveal ;
281- correct . addEventListener ( 'click' , ( ) => WWshowCorrect ( ww_id , answers ) ) ;
311+ correct . addEventListener ( 'click' , ( ) => handleWW ( ww_id , 'reveal' ) ) ;
282312 buttonContainer . appendChild ( correct ) ;
283313 }
284314
@@ -298,14 +328,11 @@ function handleWW(ww_id, action) {
298328 reset . textContent = localize_reset ;
299329 reset . addEventListener ( 'click' , ( ) => resetWW ( ww_id ) ) ;
300330 buttonContainer . appendChild ( reset )
301- } else {
302- // Update the click handler for the show correct button.
303- if ( ww_container . dataset . hasAnswer == 'true' ) {
304- const correct = buttonContainer . querySelector ( '.show-correct' ) ;
305- const correctNew = correct . cloneNode ( true ) ;
306- correctNew . addEventListener ( 'click' , ( ) => WWshowCorrect ( ww_id , answers ) ) ;
307- correct . replaceWith ( correctNew ) ;
308- }
331+ }
332+
333+ if ( runestone_logged_in && action == 'check' ) {
334+ // Runestone trigger
335+ $ ( "body" ) . trigger ( 'runestone_ww_check' , data )
309336 }
310337
311338 let iframeContents = '<!DOCTYPE html><head>' +
@@ -335,6 +362,7 @@ function handleWW(ww_id, action) {
335362 }
336363 },
337364 options: {
365+ processHtmlClass: "process-math",
338366 renderActions: {
339367 findScript: [
340368 10,
@@ -398,7 +426,11 @@ function handleWW(ww_id, action) {
398426 .graphtool-answer-container .graphtool-number-line { height: 57px; }
399427 .quill-toolbar { scrollbar-width: thin; overflow-x: hidden; }
400428 </style>` +
401- '</head><body><main class="pretext-content problem-content">' + form . outerHTML + '</main></body>' +
429+ '</head><body>' +
430+ '<div id="latex-macros" class="hidden-content process-math" style="display:none"><span class="process-math">\\(' +
431+ document . getElementById ( 'latex-macros-text' ) . textContent +
432+ '\\)</span></div>' +
433+ '<main class="pretext-content problem-content" data-iframe-height="1">' + form . outerHTML + '</main></body>' +
402434 '</html>' ;
403435
404436 let iframe ;
@@ -411,7 +443,7 @@ function handleWW(ww_id, action) {
411443 iframe . classList . add ( 'problem-iframe' ) ;
412444
413445 // Hide the static problem
414- ww_container . querySelector ( '.problem-contents' ) . classList . add ( 'hidden-content' ) ;
446+ ww_container . querySelector ( '.problem-contents' ) . classList . add ( 'hidden-content' , 'hidden' ) ;
415447
416448 if ( activate_button != null ) {
417449 // Make sure the iframe follows the activate button in the DOM
@@ -449,7 +481,7 @@ function handleWW(ww_id, action) {
449481
450482 // Place focus on the problem.
451483 ww_container . focus ( )
452- } ) ;
484+ } , "json" ) ;
453485}
454486
455487function WWshowCorrect ( ww_id , answers ) {
@@ -495,7 +527,6 @@ function WWshowCorrect(ww_id, answers) {
495527 const correct_ans = answers [ name ] . correct_choice || answers [ name ] . correct_ans ;
496528 if ( input . value == correct_ans ) {
497529 input . checked = true ;
498- //input.setAttribute('checked', 'checked');
499530 } else {
500531 input . checked = false ;
501532 }
@@ -505,10 +536,8 @@ function WWshowCorrect(ww_id, answers) {
505536 const correct_choices = answers [ name ] . correct_choices ;
506537 if ( correct_choices . includes ( input . value ) ) {
507538 input . checked = true ;
508- // input.setAttribute('checked', 'checked');
509539 } else {
510540 input . checked = false ;
511- // input.setAttribute('checked', false);
512541 }
513542 }
514543 }
@@ -564,12 +593,12 @@ function resetWW(ww_id) {
564593 iframe = ww_container . querySelector ( '.problem-iframe' ) ;
565594 iframe . remove ( ) ;
566595
567- ww_container . querySelector ( '.problem-contents' ) . classList . remove ( 'hidden-content' ) ;
596+ ww_container . querySelector ( '.problem-contents' ) . classList . remove ( 'hidden-content' , 'hidden' ) ;
568597
569598 ww_container . querySelector ( '.problem-buttons.webwork' ) . remove ( ) ;
570- ww_container . querySelector ( '.problem-buttons' ) . classList . remove ( 'hidden-content' ) ;
599+ ww_container . querySelector ( '.problem-buttons' ) . classList . remove ( 'hidden-content' , 'hidden' ) ;
571600 // if the newer activate button is there (but hidden) bring it back too
572- if ( activate_button != null ) { activate_button . classList . remove ( 'hidden-content' ) ; } ;
601+ if ( activate_button != null ) { activate_button . classList . remove ( 'hidden-content' , 'hidden' ) ; } ;
573602}
574603
575604function adjustSrcHrefs ( container , ww_domain ) {
@@ -632,7 +661,6 @@ function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_
632661 hintSol . remove ( ) ;
633662
634663 const originalDetailsContent = knowlDetails . getElementsByTagName ( 'div' ) [ 0 ] ;
635- // const newDetailsContent = originalDetailsContent.getElementsByTagName('div')[0].getElementsByTagName('div')[0];
636664 const newDetailsContent = originalDetailsContent . getElementsByTagName ( 'div' ) [ 0 ] ;
637665 newDetailsContent . className = '' ;
638666 newDetailsContent . classList . add ( hintSolType , 'solution-like' , 'knowl__content' ) ;
0 commit comments