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_processing = 'webwork2' ;
17+ const ww_origin = ww_container . dataset . origin ;
1618 const ww_problemSource = ww_container . dataset . problemsource ;
1719 const ww_sourceFilePath = ww_container . dataset . sourcefilepath ;
1820 const ww_course_id = ww_container . dataset . courseid ;
1921 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" ;
22+ const ww_passwd = ww_container . dataset . coursepassword ;
2423 const localize_submit = ww_container . dataset . localizeSubmit || "Submit" ;
2524 const localize_check_responses = ww_container . dataset . localizeCheckResponses || "Check Responses" ;
2625 const localize_reveal = ww_container . dataset . localizeReveal || "Reveal" ;
@@ -74,34 +73,71 @@ function handleWW(ww_id, action) {
7473 }
7574
7675 let url ;
76+ if ( ww_processing == 'webwork2' ) {
77+ url = new URL ( ww_domain + '/webwork2/render_rpc' ) ;
78+ }
79+ let formData = new FormData ( ) ;
7780
78- if ( action == 'check' ) {
81+ if ( action == 'check' || action == 'reveal' ) {
7982 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" ) ;
83+ formData = new FormData ( iframe . contentDocument . getElementById ( ww_id + "-form" ) ) ;
84+ formData . set ( "answersSubmitted" , '1' ) ;
85+ formData . set ( 'WWsubmit' , "1" ) ;
86+ if ( action == 'reveal' && ww_container . dataset . hasAnswer == 'true' ) {
87+ formData . set ( 'WWcorrectAnsOnly' , "1" ) ;
88+ }
89+ if ( ww_origin == 'generated' ) {
90+ const rawProblemSource = await fetch ( 'generated/webwork/pg/' + ww_problemSource ) . then ( ( r ) => r . text ( ) ) ;
91+ formData . set ( "rawProblemSource" , rawProblemSource ) ;
92+ }
93+ else if ( ww_origin == 'webwork2' ) formData . set ( "sourceFilePath" , ww_sourceFilePath ) ;
8594 } 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" ) ;
95+ formData . set ( "problemSeed" , ww_container . dataset . current_seed ) ;
96+ if ( ww_origin == 'generated' ) {
97+ const rawProblemSource = await fetch ( 'generated/webwork/pg/' + ww_problemSource ) . then ( ( r ) => r . text ( ) ) ;
98+ formData . set ( "rawProblemSource" , rawProblemSource ) ;
99+ }
100+ else if ( ww_origin == 'webwork2' ) formData . set ( "sourceFilePath" , ww_sourceFilePath ) ;
101+ formData . set ( "answersSubmitted" , '0' ) ;
102+ formData . set ( "displayMode" , "MathJax" ) ;
103+ formData . set ( "courseID" , ww_course_id ) ;
104+ formData . set ( "user" , ww_user_id ) ;
105+ formData . set ( "userID" , ww_user_id ) ;
106+ formData . set ( "passwd" , ww_passwd ) ;
107+ formData . set ( "disableCookies" , '1' ) ;
108+ formData . set ( "outputformat" , "raw" ) ;
97109 // 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 ) ;
110+ formData . set ( "showSolutions" , ww_container . dataset . hasSolution == 'true' ? '1' : '0' ) ;
111+ formData . set ( "showHints" , ww_container . dataset . hasHint == 'true' ? '1' : '0' ) ;
112+ formData . set ( "problemUUID" , ww_id ) ;
113+ }
114+
115+ // If in Runestone, check if there are previous answer submissions and get them now to use in the form.
116+ let checkboxesString = '' ;
117+ if ( runestone_logged_in && ! action ) {
118+ const answersObject = ( wwList [ ww_id . replace ( / - w w - r s $ / , '' ) ] . answers ? wwList [ ww_id . replace ( / - w w - r s $ / , '' ) ] . answers : { 'answers' : [ ] , 'mqAnswers' : [ ] } ) ;
119+ const previousAnswers = answersObject . answers ;
120+ if ( previousAnswers !== null ) {
121+ formData . set ( 'WWsubmit' , 1 ) ;
122+ }
123+ for ( const answer in previousAnswers ) {
124+ if ( previousAnswers [ answer ] . constructor === Array ) {
125+ for ( const k in previousAnswers [ answer ] ) {
126+ checkboxesString += '&' ;
127+ checkboxesString += answer ;
128+ checkboxesString += '=' ;
129+ checkboxesString += previousAnswers [ answer ] [ k ] ;
130+ }
131+ } else {
132+ formData . set ( answer , previousAnswers [ answer ] ) ;
133+ }
134+ }
101135 }
136+ // Need to get form data as a string, including possible repeated checkbox names
137+ // Do not pass post data as an object, or checkbox names will overwrite one another
138+ const formString = new URLSearchParams ( formData ) . toString ( ) ;
102139
103- // get the json and do stuff with what we get
104- $ . getJSON ( url . toString ( ) , ( data ) => {
140+ $ . post ( url , formString + checkboxesString , ( data ) => {
105141 // Create the form that will contain the text and input fields of the interactive problem.
106142 const form = document . createElement ( "form" ) ;
107143 form . id = ww_id + "-form" ;
@@ -117,6 +153,29 @@ function handleWW(ww_id, action) {
117153 // Dump the problem text, answer blanks, etc.
118154 body_div . innerHTML = data . rh_result . text ;
119155
156+ // If showPartialCorrectAnswers = 0, alter the feedback buttons according to whether or not the score is 100%.
157+ if ( 'showPartialCorrectAnswers' in data . rh_result . flags && data . rh_result . flags . showPartialCorrectAnswers == 0 ) {
158+ if ( 'score' in data . rh_result . problem_result && data . rh_result . problem_result . score >= 1 ) {
159+ body_div . querySelectorAll ( 'button.ww-feedback-btn' ) . forEach (
160+ function ( button ) {
161+ button . classList . remove ( 'btn-info' ) ;
162+ button . classList . add ( 'btn-success' ) ;
163+ button . setAttribute ( 'aria-label' , 'Correct' ) ;
164+ button . dataset . bsCustomClass = button . dataset . bsCustomClass + ' correct' ;
165+ button . dataset . bsTitle = button . dataset . bsTitle . replace ( 'Answer Preview' , 'Correct' ) ;
166+ button . firstChild . classList . add ( 'correct' )
167+ }
168+ ) ;
169+ } else {
170+ body_div . querySelectorAll ( 'button.ww-feedback-btn' ) . forEach (
171+ function ( button ) {
172+ button . setAttribute ( 'aria-label' , 'One or more answers incorrect' ) ;
173+ button . dataset . bsTitle = button . dataset . bsTitle . replace ( 'Answer Preview' , 'One or more answers incorrect' ) ;
174+ }
175+ ) ;
176+ }
177+ }
178+
120179 // Replace all hn headings with h6 headings.
121180 for ( const tag_name of [ 'h6' , 'h5' , 'h4' , 'h3' , 'h2' , 'h1' ] ) {
122181 const headings = body_div . getElementsByTagName ( tag_name ) ;
@@ -129,6 +188,19 @@ function handleWW(ww_id, action) {
129188 }
130189 }
131190
191+ // Hide textarea input and hide associated buttons
192+ var textareas = body_div . getElementsByTagName ( "textarea" ) ;
193+ for ( var i = 0 , max = textareas . length ; i < max ; i ++ )
194+ {
195+ textareas [ i ] . style . display = "none" ;
196+ textareas [ i ] . className = '' ;
197+ }
198+ var textareabuttons = body_div . querySelectorAll ( ".latexentry-preview" ) ;
199+ for ( var i = 0 , max = textareabuttons . length ; i < max ; i ++ )
200+ {
201+ textareabuttons [ i ] . remove ( ) ;
202+ }
203+
132204 adjustSrcHrefs ( body_div , ww_domain ) ;
133205
134206 translateHintSol ( ww_id , body_div , ww_domain ,
@@ -151,36 +223,6 @@ function handleWW(ww_id, action) {
151223 if ( input && input . value == '' ) {
152224 input . setAttribute ( 'value' , answers [ answer ] ) ;
153225 }
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- }
184226 }
185227 }
186228
@@ -195,7 +237,7 @@ function handleWW(ww_id, action) {
195237 courseName : ww_course_id ,
196238 courseID : ww_course_id ,
197239 user : ww_user_id ,
198- passwd : ww_course_password ,
240+ passwd : ww_passwd ,
199241 displayMode : "MathJax" ,
200242 session_key : data . rh_result . session_key ,
201243 outputformat : "raw" ,
@@ -209,7 +251,7 @@ function handleWW(ww_id, action) {
209251 } ;
210252
211253 if ( ww_sourceFilePath ) wwInputs . sourceFilePath = ww_sourceFilePath ;
212- else if ( ww_problemSource ) wwInputs . problemSource = ww_problemSource ;
254+ else if ( ww_problemSource && ww_origin == 'webwork2' ) wwInputs . problemSource = ww_problemSource ;
213255
214256 for ( const wwInputName of Object . keys ( wwInputs ) ) {
215257 const input = document . createElement ( 'input' ) ;
@@ -219,31 +261,11 @@ function handleWW(ww_id, action) {
219261 form . appendChild ( input ) ;
220262 }
221263
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-
240264 let buttonContainer = ww_container . querySelector ( '.problem-buttons.webwork' ) ;
241265 // Create the submission buttons if they have not yet been created.
242266 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' ) ; } ;
267+ // Hide the original div that contains the Activate.
268+ ww_container . querySelector ( '.problem-buttons' ) . classList . add ( 'hidden-content' , 'hidden' ) ;
247269
248270 // Create a new div for the webwork buttons.
249271 buttonContainer = document . createElement ( 'div' ) ;
@@ -262,11 +284,8 @@ function handleWW(ww_id, action) {
262284 check . style . marginRight = "0.25rem" ;
263285 check . classList . add ( 'webwork-button' ) ;
264286
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-
269287 check . textContent = runestone_logged_in ? localize_submit : localize_check_responses ;
288+
270289 check . addEventListener ( 'click' , ( ) => handleWW ( ww_id , "check" ) ) ;
271290
272291 buttonContainer . appendChild ( check ) ;
@@ -278,7 +297,7 @@ function handleWW(ww_id, action) {
278297 correct . type = "button" ;
279298 correct . style . marginRight = "0.25rem" ;
280299 correct . textContent = localize_reveal ;
281- correct . addEventListener ( 'click' , ( ) => WWshowCorrect ( ww_id , answers ) ) ;
300+ correct . addEventListener ( 'click' , ( ) => handleWW ( ww_id , 'reveal' ) ) ;
282301 buttonContainer . appendChild ( correct ) ;
283302 }
284303
@@ -298,14 +317,11 @@ function handleWW(ww_id, action) {
298317 reset . textContent = localize_reset ;
299318 reset . addEventListener ( 'click' , ( ) => resetWW ( ww_id ) ) ;
300319 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- }
320+ }
321+
322+ if ( runestone_logged_in && action == 'check' ) {
323+ // Runestone trigger
324+ $ ( "body" ) . trigger ( 'runestone_ww_check' , data )
309325 }
310326
311327 let iframeContents = '<!DOCTYPE html><head>' +
@@ -335,6 +351,7 @@ function handleWW(ww_id, action) {
335351 }
336352 },
337353 options: {
354+ processHtmlClass: "process-math",
338355 renderActions: {
339356 findScript: [
340357 10,
@@ -384,8 +401,7 @@ function handleWW(ww_id, action) {
384401 }
385402
386403 iframeContents +=
387- '<link rel="stylesheet" href="_static/pretext/css/pretext_add_on.css"/>' +
388- '<link rel="stylesheet" href="_static/pretext/css/knowls_default.css"/>' +
404+ '<link rel="stylesheet" href="_static/pretext/css/theme.css"/>' +
389405 '<script src="' + ww_domain + '/webwork2_files/node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js"></script>' +
390406 `<style>
391407 html { overflow-y: hidden; }
@@ -398,7 +414,8 @@ function handleWW(ww_id, action) {
398414 .graphtool-answer-container .graphtool-number-line { height: 57px; }
399415 .quill-toolbar { scrollbar-width: thin; overflow-x: hidden; }
400416 </style>` +
401- '</head><body><main class="pretext-content problem-content">' + form . outerHTML + '</main></body>' +
417+ '</head><body>' +
418+ '<main class="pretext-content problem-content" data-iframe-height="1">' + form . outerHTML + '</main></body>' +
402419 '</html>' ;
403420
404421 let iframe ;
@@ -411,7 +428,7 @@ function handleWW(ww_id, action) {
411428 iframe . classList . add ( 'problem-iframe' ) ;
412429
413430 // Hide the static problem
414- ww_container . querySelector ( '.problem-contents' ) . classList . add ( 'hidden-content' ) ;
431+ ww_container . querySelector ( '.problem-contents' ) . classList . add ( 'hidden-content' , 'hidden' ) ;
415432
416433 if ( activate_button != null ) {
417434 // Make sure the iframe follows the activate button in the DOM
@@ -449,7 +466,7 @@ function handleWW(ww_id, action) {
449466
450467 // Place focus on the problem.
451468 ww_container . focus ( )
452- } ) ;
469+ } , "json" ) ;
453470}
454471
455472function WWshowCorrect ( ww_id , answers ) {
@@ -495,7 +512,6 @@ function WWshowCorrect(ww_id, answers) {
495512 const correct_ans = answers [ name ] . correct_choice || answers [ name ] . correct_ans ;
496513 if ( input . value == correct_ans ) {
497514 input . checked = true ;
498- //input.setAttribute('checked', 'checked');
499515 } else {
500516 input . checked = false ;
501517 }
@@ -505,10 +521,8 @@ function WWshowCorrect(ww_id, answers) {
505521 const correct_choices = answers [ name ] . correct_choices ;
506522 if ( correct_choices . includes ( input . value ) ) {
507523 input . checked = true ;
508- // input.setAttribute('checked', 'checked');
509524 } else {
510525 input . checked = false ;
511- // input.setAttribute('checked', false);
512526 }
513527 }
514528 }
@@ -564,12 +578,12 @@ function resetWW(ww_id) {
564578 iframe = ww_container . querySelector ( '.problem-iframe' ) ;
565579 iframe . remove ( ) ;
566580
567- ww_container . querySelector ( '.problem-contents' ) . classList . remove ( 'hidden-content' ) ;
581+ ww_container . querySelector ( '.problem-contents' ) . classList . remove ( 'hidden-content' , 'hidden' ) ;
568582
569583 ww_container . querySelector ( '.problem-buttons.webwork' ) . remove ( ) ;
570- ww_container . querySelector ( '.problem-buttons' ) . classList . remove ( 'hidden-content' ) ;
584+ ww_container . querySelector ( '.problem-buttons' ) . classList . remove ( 'hidden-content' , 'hidden' ) ;
571585 // if the newer activate button is there (but hidden) bring it back too
572- if ( activate_button != null ) { activate_button . classList . remove ( 'hidden-content' ) ; } ;
586+ if ( activate_button != null ) { activate_button . classList . remove ( 'hidden-content' , 'hidden' ) ; } ;
573587}
574588
575589function adjustSrcHrefs ( container , ww_domain ) {
@@ -632,7 +646,6 @@ function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_
632646 hintSol . remove ( ) ;
633647
634648 const originalDetailsContent = knowlDetails . getElementsByTagName ( 'div' ) [ 0 ] ;
635- // const newDetailsContent = originalDetailsContent.getElementsByTagName('div')[0].getElementsByTagName('div')[0];
636649 const newDetailsContent = originalDetailsContent . getElementsByTagName ( 'div' ) [ 0 ] ;
637650 newDetailsContent . className = '' ;
638651 newDetailsContent . classList . add ( hintSolType , 'solution-like' , 'knowl__content' ) ;
0 commit comments