xowf::test_item::Answer_manager method postprocess_question_html (protected)
<instance of xowf::test_item::Answer_manager> postprocess_question_html \ -question_form question_form -achieved_points achieved_points \ -manual_grading manual_grading -submission submission \ -runtime_panel_view runtime_panel_view -exam_state exam_state \ [ -feedbackFiles feedbackFiles ]
Defined in packages/xowf/tcl/test-item-procs.tcl
Post-process the HTML of a question by adding information of the student as data attributes, such as achieved and achievable points, setting CSS classes, mangling names of composite questions to match with the data in achieved_points,
- Switches:
- -question_form (required)
- -achieved_points (required)
- -manual_grading (required)
- -submission (required, object)
- -runtime_panel_view (required)
- -exam_state (required)
- -feedbackFiles (optional)
- Returns:
- HTML block
- Testcases:
- No testcase defined.
Source code: #ns_log notice "QF=$question_form" dom parse -html -- $question_form doc $doc documentElement root if {$root eq ""} { error "form '$form' is not valid" } set per_question_points "" foreach pd [:dict_value $achieved_points details] { set qn [dict get $pd attributeName] dict set per_question_points $qn achieved [dict get $pd achieved] dict set per_question_points $qn achievable [dict get $pd achievable] } # # The aggregated form (method :aggregated_form) is generated # before submissions are available. For e.g., the exam protocol, # the same grading-box with the same raw data will be reused # potentially per submission. To ensure uniqueness of the HTML # IDs for the dialogs, we have to fill in the submission IDs. # set grading_boxes [$root selectNodes {//div[contains(@class,'grading-box')]}] foreach grading_box $grading_boxes { $grading_box setAttribute id [$grading_box getAttribute id]-[$submission item_id] } # # For every composite question: # # - update the question_name of the subquestion by prefixing it # with the name of the composite, since this is what we have # in the details of achieved_points. # - hide the grading box of the composite # - unhide the grading box of the composite children # set composite_grading_boxes [$root selectNodes {//div[@data-item_type='Composite']/div[contains(@class,'grading-box')]}] foreach composite_grading_box $composite_grading_boxes { set composite_qn [$composite_grading_box getAttribute "data-question_name"] set parentNode [$composite_grading_box parentNode] :dom class add $composite_grading_box {.} [::template::CSS class d-none] foreach grading_box [$parentNode selectNodes {div//div[contains(@class,'grading-box')]}] { set qn [$grading_box getAttribute data-question_name] regsub {^answer_} $qn ${composite_qn}_ new_qn #ns_log notice "CHILD of Composite: rename QN from $qn to $new_qn" $grading_box setAttribute data-question_name $new_qn $grading_box setAttribute id ${composite_qn}_[$grading_box getAttribute id] :dom class remove $grading_box {.} [::template::CSS class d-none] # # The composite questions are prerendered and do not have # hint boxes, since we do not want to have even hidden in # the HTML rendering show to the student during the # exam. Therefore, we add these now for the exam protocol in # an extra step. We try to add here both, feedback and # correction notes (if available). The loop over all grading # boxes below should care for the visibility of the hint # boxes due to percentages. # if {[$grading_box hasAttribute data-question_id]} { set subquestion_id [$grading_box getAttribute data-question_id] set subquestion_obj [::xowiki::FormPage get_instance_from_db -item_id $subquestion_id] #ns_log notice "CHILD of Composite has form_id $subquestion_id [nsf::is object ::$subquestion_id]" set HTML [:QM hint_boxes -question_obj $subquestion_obj -with_feedback=1 -with_correction_notes=1] if {$HTML ne ""} { dom parse -simple -html <body>$HTML</body> hintsDoc $hintsDoc documentElement hintsBody foreach child $hintsBody { [$grading_box parentNode] appendChild $child } } } else { # # Probably some legacy item # ::util_user_message -message "Composite Exercise looks like legacy data; please edit+save" ad_log warning "composite_grading_box has no data-question_id" } } } # # Composite grading-boxes are done, now general code over all # grading-boxes. # set submission_state [$submission state] #set noManualGrading [expr {$submission_state ne "done" || $exam_state eq "published"}] set noManualGrading [expr {$exam_state eq "published"}] set grading_boxes [$root selectNodes {//div[contains(@class,'grading-box')]}] foreach grading_box $grading_boxes { set qn [$grading_box getAttribute "data-question_name"] set item_node [$grading_box parentNode] set item_type [expr {[$item_node hasAttribute "data-item_type"] ? [$item_node getAttribute "data-item_type"] : ""}] set feedbackFilesHTML [:render_feedback_files -question_name $qn -feedbackFiles $feedbackFiles] #ns_log notice "FEEDBACK '$qn' feedbackFiles $feedbackFiles HTML\n$feedbackFilesHTML" #ns_log notice "... QN '$qn' item_type '$item_type'" "submission state $submission_state" "exam state $exam_state noManualGrading $noManualGrading" if {$noManualGrading} { :dom class add $grading_box {a[contains(@class,'manual-grade')]} [::template::CSS class d-none] } # # Get manual gradings, if these were already provided. # if {[dict exists $manual_grading $qn achieved]} { set achieved [dict get $manual_grading $qn achieved] } else { set achieved "" } if {[dict exists $manual_grading $qn comment]} { set comment [dict get $manual_grading $qn comment] } else { set comment "" } if {[dict exists $per_question_points $qn achieved]} { # # Manual grading has higher priority than automated grading. # if {$achieved eq ""} { set achieved [dict get $per_question_points $qn achieved] } set achievable [dict get $per_question_points $qn achievable] $grading_box setAttribute data-autograde 1 } else { set achievable "" } #ns_log notice "... QN '$qn' item_type $item_type achieved '$achieved' achievable '$achievable'" set percentage "" if {$achieved eq ""} { set warning [::template::icon -class [template::CSS class text-warning] -name warn ] set pencil [::template::icon -name pencil] :dom node replaceXML $grading_box {span[@class='points']} [dict get $warning HTML] :dom node replaceXML $grading_box {a[@class='manual-grade-edit']/span/..} [dict get $pencil HTML] # # The last case with "span/.." is for legacy cases, where # composite items were generated before bootstrap5 support # and/or where composite items were generated under # bootstrap5 but are rendered with bootstrap3 # :dom node replaceXML $grading_box {a[@class='manual-grade']/span/..} [dict get $pencil HTML] } else { :dom node replace $grading_box {span[@class='points']} {::html::t $achieved} if {$achievable ne ""} { set percentage [format %.2f [expr {$achieved*100.0/$achievable}]] :dom node replace $grading_box {span[@class='percentage']} {::html::t ($percentage%)} } } # # handling of legacy items # set changes [expr {[::template::CSS toolkit] eq "bootstrap" ? {bs-toggle toggle bs-target target} : {toggle bs-toggle target bs-target}}] foreach node [$grading_box selectNodes {a[@class='manual-grade']}] { foreach {old new} $changes { if {[$node hasAttribute data-$old]} { $node setAttribute data-$new [$node getAttribute data-$old] $node removeAttribute data-$old } } } if {$feedbackFilesHTML ne ""} { #ns_log notice "REPLACE thumbnail-files-wrapper in\n[$grading_box asXML]" if {[llength [$grading_box selectNodes {div[@class='thumbnail-files-wrapper']}]] == 0} { # # Must be a legacy composite item without the thumbnail # wrapper. # $grading_box appendXML {<div class="thumbnail-files-wrapper"></div>} } :dom node replaceXML $grading_box {div[@class='thumbnail-files-wrapper']} $feedbackFilesHTML } # # When "comment" is empty, do not show the label. # :dom node replace $grading_box {span[@class='comment']} {::html::t $comment} if {$comment eq ""} { :dom class add $grading_box {span[@class='feedback-label']} [::template::CSS class d-none] } else { :dom class remove $grading_box {span[@class='feedback-label']} [::template::CSS class d-none] } $grading_box setAttribute data-user_id [$submission creation_user] $grading_box setAttribute data-user_name [$submission set online-exam-userName] $grading_box setAttribute data-full_name [$submission set online-exam-fullName] $grading_box setAttribute data-achieved $achieved $grading_box setAttribute data-achievable $achievable $grading_box setAttribute data-comment $comment $grading_box setAttribute data-link [::[$submission package_id] make_link $submission file-upload] # # Feedback handling (should be merged with the individual feedback) # set correct_feedback_node [$item_node selectNodes {div[contains(@class,'feedback-correct')]}] set incorrect_feedback_node [$item_node selectNodes {div[contains(@class,'feedback-incorrect')]}] set correction_notes_node [$item_node selectNodes {div[contains(@class,'correction-notes')]}] if {$percentage ne "" && $percentage < 50 && $incorrect_feedback_node ne ""} { # # Remove positive and keep negative feedback. # if {$correct_feedback_node ne ""} { $correct_feedback_node delete set correct_feedback_node "" } } if {$correct_feedback_node ne "" && $incorrect_feedback_node ne ""} { # # If we still have a positive feedback, remove negative # feedback. # $incorrect_feedback_node delete } # # In student review mode ('Einsicht'), remove # - correction notes, and # - edit controls. # if {$runtime_panel_view eq "student"} { if {$correction_notes_node ne ""} { $correction_notes_node delete } :dom node delete $grading_box {a} } } return [$root asHTML]XQL Not present: Generic, PostgreSQL, Oracle