- Methods: All Methods Documented Methods Hide Methods
- Source: Display Source Hide Source
- Variables: Show Variables Hide Variables
Class ::xowf::test_item::Answer_manager
::xowf::test_item::Answer_manager create ...Public API: - create_workflow - delete_all_answer_data - allow_answering - get_answer_wf - get_wf_instances - get_answer_attributes - student_submissions_exist - runtime_panel - render_answers_with_edit_history - render_answers - marked_results - answers_panel - exam_results - grading_table - grading_scheme - grade - participants_table - get_duration - get_IPs - revisions_up_to - last_time_in_state - last_time_switched_to_state - state_periods - time_window_setup - waiting_room_message
Defined in packages/xowf/tcl/test-item-procs.tcl
Class Relations
::nx::Class create ::xowf::test_item::Answer_manager \ -superclass ::xowf::test_item::AssessmentInterfaceMethods (to be applied on instances)
allow_answering (scripted, public)
<instance of xowf::test_item::Answer_manager> allow_answering \ [ -examwf examwf ] -ip ipTell if specified IP address is allowed to answer the exam.
- Switches:
- -examwf (optional, object)
- -ip (required)
- Returns:
- boolean
- Testcases:
- No testcase defined.
set iprange [$examwf property iprange] if {$iprange ne ""} { set iprangeObj ::xowf::iprange::$iprange if {$iprange ne "all" && (![nsf::is object $iprangeObj] || ![$iprangeObj allow_access $ip] )} { ns_log notice "ANSWER: [list $iprangeObj allow_access $ip] ->" [$iprangeObj allow_access $ip] return 0 } } return 1answer_form_field_objs (scripted, public)
<instance of xowf::test_item::Answer_manager> answer_form_field_objs \ [ -clear ] [ -wf wf ] [ -generic ] form_infoInstantiate the form_field objects of the provided form based on form_info.
- Switches:
- -clear (optional)
- -wf (optional, object)
- -generic (optional)
- Parameters:
- form_info (required)
- Testcases:
- No testcase defined.
set key ::__test_item_answer_form_fields if {$clear} { # # The -clear option is needed, when there are multiple # assessments protocols/tables on the same page (currently # not). # unset -nocomplain $key } else { #ns_log notice "### answer_form_field_objs key exists [info exists $key]" if {![info exists $key]} { #ns_log notice "form_info: $form_info" set fc [lsort -unique [dict get $form_info disabled_form_constraints]] #ns_log notice "### FC $fc" set pc_params [::xo::cc perconnection_parameter_get_all] if {$generic} { set fc [:replace_in_fc -fc $fc shuffle_kind none] set fc [:replace_in_fc -fc $fc show_max ""] } set $key [$wf create_form_fields_from_form_constraints -lookup $fc] ::xo::cc perconnection_parameter_set_all $pc_params $wf form_field_index [set $key] } return [set $key] }answers_panel (scripted, public)
<instance of xowf::test_item::Answer_manager> answers_panel \ [ -polling ] [ -heading heading ] \ [ -submission_msg submission_msg ] [ -manager_obj manager_obj ] \ [ -target_state target_state ] [ -wf wf ] \ [ -current_question current_question ] [ -extra_text extra_text ]Produce HTML code for an answers panel, containing the number of participants of an e-assessment and the number of participants, who have already answered.
- Switches:
- -polling (optional, defaults to
"false"
)- when specified, provide live updates of the numbers via AJAX calls
- -heading (optional, defaults to
"#xowf.submitted_answers#"
)- -submission_msg (optional, defaults to
"#xowf.participants_answered_question#"
)- -manager_obj (optional, object)
- -target_state (optional)
- -wf (optional, object)
- -current_question (optional)
- -extra_text (optional)
- optional extra text for the panel, has to be provided with valid HTML markup.
- Testcases:
- No testcase defined.
set answers [:get_answer_attributes $wf] set nrParticipants [llength $answers] if {$current_question ne ""} { set answered [:FL answers_for_form [$current_question name] $answers] } else { set answered [:get_answer_attributes -state $target_state $wf] } set nrAnswered [llength $answered] set answerStatus [::xowiki::bootstrap::card -title $heading -body [subst {<p><span id='answer-status'>$nrAnswered/$nrParticipants</span> $submission_msg<p>$extra_text}]] if {$polling} { # # Auto refresh of number of participants and submissions when # polling is on. # set url [$manager_obj pretty_link -query m=poll] template::add_body_script -script [subst -nocommands { (function poll() { setTimeout(function() { var xhttp = new XMLHttpRequest(); xhttp.open("GET", '$url', true); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var data = xhttp.responseText; var el = document.querySelector('#answer-status'); el.innerHTML = data; poll(); //activate links if a users started the exam var answers = data.split('/'); if (answers.length == 2 && answers[1] > 0) { var disabledLinkItems = document.querySelectorAll(".list-group-item.link-disabled"); disabledLinkItems.forEach(function(linkItem) { linkItem.classList.remove("link-disabled"); }); } } }; xhttp.send(); }, 1000); })(); }] } return $answerStatuscountdown_timer (scripted, public)
<instance of xowf::test_item::Answer_manager> countdown_timer \ -target_time target_time -id id [ -audio_alarm on|off ] \ [ -audio_alarm_cookie audio_alarm_cookie ] \ [ -audio_alarm_times audio_alarm_times ]Accepted formats for target_time, determined by JavaScript ISO 8601, e.g. YYYY-MM-DDTHH:mm:ss.sss" Set current time based on host time instead of new Date().getTime() to avoid surprises, in cases, the time at the client browser is set incorrectly.
- Switches:
- -target_time (required)
- -id (required)
- -audio_alarm (optional, boolean, defaults to
"true"
)- -audio_alarm_cookie (optional, defaults to
"incass_exam_audio_alarm"
)- -audio_alarm_times (optional, defaults to
"60,30,20,10,5,2"
)- Testcases:
- No testcase defined.
set nowMs [clock milliseconds] set nowIsoTime [clock format [expr {$nowMs/1000}] -format "%Y-%m-%dT%H:%M:%S"].[format %.3d [expr {$nowMs % 1000}]] template::add_body_script -script [subst { var countdown_target_date = new Date('$target_time').getTime(); var countdown_days, countdown_hours, countdown_minutes, countdown_seconds; var countdown = document.getElementById('$id'); // adjust target time by the difference between the host and client time countdown_target_date = countdown_target_date - (new Date('$nowIsoTime').getTime() - new Date().getTime()); setInterval(function () { var current_date = new Date().getTime(); var absolute_seconds_left = (countdown_target_date - current_date) / 1000; var seconds_left = absolute_seconds_left var HTML = ''; countdown_days = parseInt(seconds_left / 86400); seconds_left = seconds_left % 86400; countdown_hours = parseInt(seconds_left / 3600); seconds_left = seconds_left % 3600; countdown_minutes = parseInt(seconds_left / 60); countdown_seconds = parseInt(seconds_left % 60); var alarmseconds = countdown.parentNode.dataset.alarmseconds; if (typeof alarmseconds !== 'undefined') { var full_seconds = Math.trunc(absolute_seconds_left); // for testing purposes, use: (full_seconds % 5 == 0) if (alarmseconds.includes(full_seconds)) { beep(200); } } if (seconds_left < -60) { countdown.innerHTML = "<span style='color:red;'> [_ xowf.Countdown_timer_expired]</span>" return } if (countdown_days != 0) { HTML += '<span class="days">' + countdown_days + ' <b> ' + (countdown_days != 1 ? '[_ xowf.Days]' : '[_ xowf.Day]') + '</b></span> '; } if (countdown_hours != 0 || countdown_days != 0) { HTML += '<span class="hours">' + countdown_hours + ' <b> ' + (countdown_hours != 1 ? '[_ xowf.Hours]' : '[_ xowf.Hour]') + '</b></span> '; } HTML += '<span class="minutes">' + countdown_minutes + ' <b> ' + (countdown_minutes != 1 ? '[_ xowf.Minutes]' : '[_ xowf.Minute]') + '</b></span> ' + '<span class="seconds">' + countdown_seconds + ' <b> ' + (countdown_seconds != 1 ? '[_ xowf.Seconds]' : '[_ xowf.Second]') + '</b></span> [_ xowf.remaining]' ; countdown.innerHTML = HTML; }, 1000); var beep = (function () { return function (duration, finishedCallback) { var container = document.getElementById('$id').parentNode; //console.log("beep attempt " + duration + ' ' + audioContext + ' ' + container.dataset.alarm); if (typeof audioContext !== 'undefined' && (container.dataset.alarm == 'active')) { //console.log("true beep duration " + duration + ' ' + audioContext + ' ' + audioContext.state); var osc = audioContext.createOscillator(); osc.type = "sine"; osc.connect(audioContext.destination); if (osc.noteOn) osc.noteOn(0); // old browsers if (osc.start) osc.start(); // new browsers setTimeout(function () { if (osc.noteOff) osc.noteOff(0); // old browsers if (osc.stop) osc.stop(); // new browsers }, duration); } }; })(); }] if {$audio_alarm} { # # Audio alarm handling is more tricky than expected, since # modern browsers do not allow one to create an active sound # context without a "user gesture" (requires e.g. a click to # start). # # The code tries to remember the audio state between different # pages, such when e.g. being in an exam, the user has to # activate/deactivate the audio not on every page. However, # when the user does a full reload, then the user has to # activate the audio alarm again. # # The state is symbolized using bootstrap 3 glyphicons or # bootstrap icons. The code is tested primarily with chrome. # template::add_body_script -script [subst { var audioContext = new AudioContext(); var audioContext_setSate = (function (targetState) { var container = document.getElementById('$id').parentNode; //console.log('--- state = ' + audioContext.state + ' want ' + targetState); if (targetState == 'active') { var elements = container.querySelector('i'); var prefix = 'bi'; if (!elements) { elements = container.querySelector('span'); prefix = 'glyphicon'; } elements.classList.remove(prefix + '-volume-off'); elements.classList.add(prefix + '-volume-up'); container.dataset.alarm = 'active'; document.cookie = '$audio_alarm_cookie=active; sameSite=strict'; audioContext.resume().then(() => {console.log('Playback resumed successfully ' + targetState);}); } else { var elements = container.querySelector('i'); var prefix = 'bi'; if (!elements) { elements = container.querySelector('span'); prefix = 'glyphicon'; } elements.classList.remove(prefix + '-volume-up'); elements.classList.add(prefix + '-volume-off'); container.dataset.alarm = 'inactive'; document.cookie = '$audio_alarm_cookie=inactive; sameSite=strict'; audioContext.suspend().then(() => {console.log('Playback suspended successfully ' + targetState);}); } //console.log('setSate ' + audioContext.state + ' alarm ' + container.dataset.alarm); }); var audioContext_toggle = (function (event) { var container = document.getElementById('$id').parentNode; //console.log('audioContext_toggle ' + audioContext.state); if (container.dataset.alarm != 'active') { audioContext_setSate('active'); beep(200); } else { audioContext_setSate('inactive'); } }); var audioContext_onload = (function (event) { var m = document.cookie.match('(^|;)\\s*$audio_alarm_cookie\\s*=\\s*(\[^;\]+)'); var cookieValue = (m ? m.pop() : 'inactive'); console.log('audioContext_onload ' + audioContext.state + ' cookie ' + cookieValue); // // When the current state is 'running' the behavior seems // cross browser uniform, we can set it to the state we got // from the cookie. // if (audioContext.state == 'running') { audioContext_setSate(cookieValue); } else { // // FireFox can switch to "active" after reload, while // this does not work on Chrome and friends. // if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) { audioContext_setSate(cookieValue); } else { audioContext_setSate('inactive'); } } }); console.log('onload'); console.log(document.getElementById('$id')); console.log('register audiocontext_toggle'); document.getElementById('$id').parentNode.addEventListener('click', audioContext_toggle); window.addEventListener('load', audioContext_onload); }] if {[ns_conn isconnected]} { # # The icon names "volume-off" and "volume-up" exist in the # glyph icons and for the bootstrap icons (Bootstrap 5) # set alarmState [ns_getcookie $audio_alarm_cookie "inactive"] set icon [expr {$alarmState eq "inactive" ? "volume-off":"volume-up"}] } else { set alarmState "inactive" set icon "volume-off" } #ns_log notice "C=$alarmState" return [subst { <div data-alarm='$alarmState' data-alarmseconds='\[$audio_alarm_times\]'> <adp:icon name='$icon'> <div style='display: inline-block;' id='$id'></div> </div> }] } else { return [subst { <div style='display: inline-block;' id='$id'></div> }] }create_workflow (scripted, public)
<instance of xowf::test_item::Answer_manager> create_workflow \ [ -answer_workflow answer_workflow ] \ [ -master_workflow master_workflow ] parentObjCreate a workflow based on the template provided in this method for answering the question for the students. The name of the workflow is derived from the workflow instance and recorded in the formfield "wfName". :log "create_answer_workflow $parentObj"
- Switches:
- -answer_workflow (optional, defaults to
"/packages/xowf/lib/online-exam-answer.wf"
)- -master_workflow (optional, defaults to
"en:Workflow.form"
)- Parameters:
- parentObj (required, object)
- Testcases:
- No testcase defined.
# first delete workflow and data, when it exists if {[$parentObj property wfName] ne ""} { set wf [:delete_all_answer_data $parentObj] if {$wf ne ""} {$wf delete} } # # Create a fresh workflow (e.g. instance of the online-exam, # inclass-quiz, ...). # set wfName [$parentObj name].wf $parentObj set_property -new 1 wfName $wfName set wfTitle [$parentObj property _title] set questionObjs [:QM question_objs $parentObj] set wfQuestionNames {} set wfQuestionTitles {} set attributeNames {} foreach form_obj $questionObjs { lappend attributeNames [:FL form_name_based_attribute_stem [$form_obj name]] lappend wfQuestionNames ../[$form_obj name] lappend wfQuestionTitles [$form_obj title] } set wfID [$parentObj item_id] set wfDef [subst -nocommands { set wfID $wfID set wfQuestionNames [list $wfQuestionNames] xowf::include $answer_workflow }] set attributeNames [join $attributeNames ,] #:log "create workflow by filling out form '$master_workflow'" set WF [::[$parentObj package_id] instantiate_forms -parent_id [$parentObj parent_id] -forms $master_workflow -default_lang [$parentObj lang]] set fc {} lappend fc "@table:_item_id,_state,$attributeNames,_last_modified" "@table_properties:view_field=_item_id" @cr_fields:hidden set wf [$WF create_form_page_instance -name $wfName -nls_language [$parentObj nls_language] -publish_status ready -parent_id [$parentObj item_id] -package_id [$parentObj package_id] -default_variables [list title $wfTitle] -instance_attributes [list workflow_definition $wfDef form_constraints $fc]] $wf save_new #ns_log notice "create_answer_workflow $wf DONE [$wf pretty_link] IA <[$wf instance_attributes]>" #ns_log notice "create_answer_workflow parent $parentObj IA <[$parentObj instance_attributes]>" set time_window [$parentObj property time_window] if {$time_window ne ""} { :time_window_setup $parentObj -time_window $time_window }delete_all_answer_data (scripted, public)
<instance of xowf::test_item::Answer_manager> delete_all_answer_data \ objDelete all instances of the answer workflow
- Parameters:
- obj (required, object)
- Testcases:
- No testcase defined.
set wf [:get_answer_wf $obj] if {$wf ne ""} { set items [:get_wf_instances -initialize false $wf] foreach i [$items children] { $i www-delete } } # # Delete as well the manual gradings for this exam. # #$obj set_property -new 1 manual_gradings {} :AM set_exam_results -obj $obj manual_gradings {} return $wfdelete_scheduled_atjobs (scripted, public)
<instance of xowf::test_item::Answer_manager> delete_scheduled_atjobs \ objDelete previously scheduled atjobs ns_log notice "#### delete_scheduled_atjobs"
- Parameters:
- obj (required, object)
- Testcases:
- No testcase defined.
set item_id [$obj item_id] set atjob_form_id [::xowf::atjob form_id -parent_id $item_id -package_id [ad_conn package_id]] set to_delete [xo::dc list get_children { select item_id from xowiki_form_instance_item_index where parent_id = :item_id and page_template = :atjob_form_id }] foreach id $to_delete { ns_log notice "#### acs::dc call content_item proc delete -item_id $id" acs::dc call content_item delete -item_id $id }exam_results (scripted, public)
<instance of xowf::test_item::Answer_manager> exam_results \ [ -manual_gradings manual_gradings ] \ [ -gradingScheme gradingScheme ] [ -only_grades on|off ] \ [ -reply ] [ -format format ] [ -orderby orderby ] results_dictReturn results either as HTML table, as HTML chart or as csv. When "reply" is set. the result is returned directly to the browser (for downloading). When "gradingScheme" is empty, this method returns the following fields: participant, question, achieved_points, achievable points, comment When the "gradingScheme" is specified the results are per-participant. In this cases, when the "gradingScheme" is "....::none", the fields are participant, achieved, percentage otherwise the grade and rounding of achieved points and percentage are exported based on the rules of the grading scheme. participant, achieved, percentage, grade When additionally "only_grades" is specified, just participant and grad are returned/exported.
- Switches:
- -manual_gradings (optional)
- -gradingScheme (optional)
- needed for reporting grades, can be empty
- -only_grades (optional, boolean, defaults to
"false"
)- -reply (optional, defaults to
"false"
)- when false, csv will be returned as text, when true, it will be returned as response to the browser.
- -format (optional, defaults to
"csv"
)- -orderby (optional, defaults to
"participant,desc"
)- Parameters:
- results_dict (required)
- the results to format as csv, every key in the dict represents a user_id.
- Returns:
- csv as value or as response to the client
- Testcases:
- No testcase defined.
set result "" if {$gradingScheme eq ""} { set t [:result_table per_question -manual_gradings $manual_gradings $results_dict] } else { set t [:result_table per_participant -gradingScheme $gradingScheme -only_grades $only_grades -manual_gradings $manual_gradings $results_dict] } lassign [split $orderby ,] att order $t orderby -order [expr {$order eq "asc" ? "increasing" : "decreasing"}] -type [ad_decode $att achieved real achievable real grade integer dictionary] $att # # XLS export requires OOXML # # See https://fossil.sowaswie.de/ooxml/index # if {$format eq "xls" && [::namespace which ::ooxml::xl_write] eq ""} { set format csv } if {$reply} { switch $format { html { ns_return 200 "text/html; charset=utf-8" [$t asHTML] ad_script_abort } xls { set s [::ooxml::xl_write new] set sheet [$s worksheet {1}] set decimal [lc_get "decimal_point"] set doublestyle [$s style -numfmt [$s numberformat -decimal -format "#${decimal}##"]] set stringstyle [$s style -numfmt [$s numberformat -string]] set datestyle [$s style -numfmt [$s numberformat -date]] set cellformat {} #iterate cols of table $s row $sheet set displayColumns [lmap column [${t}::__columns children] { if {[$column exists no_csv]} continue if {[$column istype ::xo::Table::BulkAction]} continue if {[$column istype ::xo::Table::HiddenField]} continue set column }] foreach column $displayColumns { if {[$column name] in {"achieved" "achievable" "percentage"}} { lappend cellformat double } else { lappend cellformat string } set label [$column label] if {[regexp {^#([a-zA-Z0-9_:-]+\.[a-zA-Z0-9_:-]+)#$} $label _ message_key]} { set label [_ $message_key] } set value [string map {\" \\\" \n \r} $label] $s cell $sheet $value } #iterate row content foreach row [$t children] { $s row $sheet set i 0 foreach column $displayColumns { set value [string map {\" \\\" \n \r} [$row set [$column set name]]] set format [lindex $cellformat $i] $s cell $sheet $value -style [set [set format]style] incr i } } $s write results.xlsx ad_script_abort } default {set result [$t write_csv]} } } else { switch $format { chart {set result [:grading_table [$t set __grade_dict]]} html {set result [$t asHTML]} default {set result [$t format_csv]} } } $t destroy return $resultexport_answer (scripted, public)
<instance of xowf::test_item::Answer_manager> export_answer \ [ -combined_form_info combined_form_info ] -html html \ -recutil recutil [ -submission submission ]Export the provided question and answer in GNU rectuil format. ns_log notice "answers: [$submission serialize]"
- Switches:
- -combined_form_info (optional)
- -html (required)
- -recutil (required, object)
- -submission (optional, object)
- Testcases:
- No testcase defined.
if {[$submission exists __form_fields]} { set form_fields [$submission set __form_fields] } else { # # We do not have the newest version of xowiki, so locate the # objs the hard way based on the naming convention. # set form_field_objs [lmap f [::xowiki::formfield::FormField info instances -closure] { if {![string match *_ [$f name]]} {continue} set f }] foreach form_field_obj $form_field_objs { dict set form_fields [$form_field_obj name] $form_field_obj } ns_log notice "export_answers: old style form_fields: $form_fields" } set export_dict "" set user [$submission set creation_user] if {![info exists ::__running_ids]} { set ::__running_ids "" } if {![dict exists $::__running_ids $user]} { dict set ::__running_ids $user [incr ::__running_id] } set seeds [$submission property seeds] set instance_attributes [$submission set instance_attributes] set answer_attributes [lmap a $instance_attributes { if {![string match *_ $a]} {continue} set a }] #ns_log notice "export_answers: combined_form_info: $combined_form_info" #set title_infos [dict get $combined_form_info title_infos] # # Get the question dict, which is a mapping between question # names and form_obj_ids. # set question_dict [:FL name_to_question_obj_dict [dict get $combined_form_info question_objs]] # ns_log notice "export_answers: question_dict: $question_dict" set form_constraints [lsort -unique [dict get $combined_form_info form_constraints]] set fc_dict [:fc_to_dict $form_constraints] #ns_log notice "... form_constraints ([llength $form_constraints]) $form_constraints" #ns_log notice ".... dict $fc_dict" # # Every answer_attribute contains the answer to a test_item # (which potentially sub answers). # foreach a $answer_attributes { #ns_log notice "answers <[dict get $instance_attributes $a]>" foreach {alternative_id answer} [dict get $instance_attributes $a] { set alt_value [lindex [split $alternative_id .] 1] set form_obj [dict get $question_dict $a] #set ff [dict get $form_fields $a] #ns_log notice "answer $a: [dict get $instance_attributes $a] [$ff serialize]" #ns_log notice "answer $a: form_obj [$form_obj serialize]" set form_obj_ia [$form_obj instance_attributes] #ns_log notice "answer $a: [dict get $instance_attributes $a] [dict keys [dict get $form_obj_ia question]]" #ns_log notice "INTERACTION [dict get [dict get $form_obj_ia question] question.interaction]" set intro [dict get [dict get [dict get $form_obj_ia question] question.interaction] question.interaction.text] #ns_log notice "TEXT $intro" #set question_title [question_manager question_property $form_obj title] #set question_minutes [question_manager question_property $form_obj minutes] #ns_log notice "answer $a: [dict get $instance_attributes $a] [dict keys [dict get $form_obj_ia question]]" #dict set export_dict name $a dict set export_dict name $alternative_id dict set export_dict user_id $user dict set export_dict running_id [dict get $::__running_ids $user] dict set export_dict question_obj $form_obj dict set export_dict question_title [$form_obj title] dict set export_dict question_intro [ns_striphtml $intro] dict set export_dict question_minutes [dict get $fc_dict $a test_item_minutes] dict set export_dict question_points [dict get $fc_dict $a test_item_points] dict set export_dict question_text [ns_striphtml [:get_label_from_options $alt_value [dict get $fc_dict $a options]]] #dict set export_dict options [dict get $fc_dict $a options] dict set export_dict answer $answer ns_log notice "answer $a: DICT $export_dict" #ns_log notice "avail $a: [dict get $fc_dict $a]" $recutil ins $export_dict } }get_IPs (scripted, public)
<instance of xowf::test_item::Answer_manager> get_IPs \ revision_setsGet the IP addresses for the given revision set. Should be actually only one. The revision_set must not be empty.
- Parameters:
- revision_sets (required)
- Testcases:
- No testcase defined.
set IPs "" foreach revision_set $revision_sets { set ip [ns_set get $revision_set creation_ip] if {$ip ne ""} { dict set IPs [ns_set get $revision_set creation_ip] 1 } } return [dict keys $IPs]get_answer_attributes (scripted, public)
<instance of xowf::test_item::Answer_manager> get_answer_attributes \ [ -state state ] [ -extra_attributes extra_attributes ] wfExtracts wf instances as answers (e.g., extracting their answer-specific attributes)
- Switches:
- -state (optional)
- retrieve only instances in this state
- -extra_attributes (optional)
- return these attributes additionally as key/value pairs per tuple
- Parameters:
- wf (required, object)
- the workflow
- Returns:
- a list of dicts
- Testcases:
- No testcase defined.
set results {} set items [:get_wf_instances $wf] foreach i [$items children] { if {$state ne "" && [$i state] ne $state} { continue } set answerAttributes [:FL answer_attributes [$i instance_attributes]] foreach extra $extra_attributes { lappend answerAttributes $extra [$i property $extra] } #ns_log notice "get_answer_attributes $i: <$answerAttributes> ALL [$i instance_attributes]" lappend results [list item $i answerAttributes $answerAttributes state [$i state]] } return $resultsget_answer_wf (scripted, public)
<instance of xowf::test_item::Answer_manager> get_answer_wf objreturn the workflow denoted by the property wfName in obj
- Parameters:
- obj (required, object)
- Testcases:
- No testcase defined.
return [::[$obj package_id] instantiate_forms -parent_id [$obj item_id] -default_lang [$obj lang] -forms [$obj property wfName]]get_duration (scripted, public)
<instance of xowf::test_item::Answer_manager> get_duration \ [ -exam_published_time exam_published_time ] revision_setsGet the duration from a set of revisions and return a dict containing "from", "fromClock","to", "toClock", "seconds", and "duration".
- Switches:
- -exam_published_time (optional)
- Parameters:
- revision_sets (required)
- Testcases:
- No testcase defined.
set first [lindex $revision_sets 0] set last [lindex $revision_sets end] set fromClock [clock scan [::xo::db::tcl_date [ns_set get $first creation_date] tz]] set toClock [clock scan [::xo::db::tcl_date [ns_set get $last last_modified] tz]] dict set r fromClock $fromClock dict set r toClock $toClock dict set r from [clock format $fromClock -format "%H:%M:%S"] dict set r to [clock format $toClock -format "%H:%M:%S"] set timeDiff [expr {$toClock - $fromClock}] dict set r duration "[expr {$timeDiff/60}]m [expr {$timeDiff%60}]s" dict set r seconds $timeDiff if {$exam_published_time ne ""} { set examPublishedClock [clock scan [::xo::db::tcl_date $exam_published_time tz]] dict set r examPublishedClock $examPublishedClock dict set r examPublished [clock format $examPublishedClock -format "%H:%M:%S"] set epTimeDiff [expr {$toClock - $examPublishedClock}] dict set r examPublishedDuration "[expr {$epTimeDiff/60}]m [expr {$epTimeDiff%60}]s" #ns_log notice "EP examPublishedDuration [dict get $r examPublishedDuration]" "EP [dict get $r examPublished] $exam_published_time" dict set r examPublishedSeconds $epTimeDiff } return $rget_exam_results (scripted, public)
<instance of xowf::test_item::Answer_manager> get_exam_results \ -obj obj property [ default ]Retrieve a property value from the exam statistics result page. This page is an instance of the exam statistics workflow stored as a child of the exam object.
- Switches:
- -obj (required, object)
- the exam object
- Parameters:
- property (required)
- the property name
- default (optional)
- default value when property is not found
- Testcases:
- No testcase defined.
set p [$obj childpage -name en:result -form inclass-exam-statistics.wf] set instance_attributes [$p instance_attributes] if {[dict exists $instance_attributes $property]} { #ns_log notice "get_exam_results <$property> returns value from " "results page: [dict get $instance_attributes $property]" return [dict get $instance_attributes $property] } #ns_log notice "get_exam_results <$property> returns default" return $defaultget_wf_instances (scripted, public)
<instance of xowf::test_item::Answer_manager> get_wf_instances \ [ -initialize initialize ] [ -orderby orderby ] \ [ -creation_user creation_user ] [ -item_id item_id ] \ [ -state state ] wfget_wf_instances: return the workflow instances
- Switches:
- -initialize (optional, defaults to
"false"
)- -orderby (optional)
- -creation_user (optional, integer)
- -item_id (optional, integer)
- -state (optional)
- Parameters:
- wf (required, object)
- Testcases:
- No testcase defined.
:assert_assessment_container $wf set extra_where_clause "" foreach var {creation_user item_id state} { if {[info exists $var]} { append extra_where_clause "AND $var = [ns_dbquotevalue [set $var]] " } } return [::xowiki::FormPage get_form_entries -base_item_ids [$wf item_id] -form_fields "" -always_queried_attributes "*" -initialize $initialize -orderby $orderby -extra_where_clause $extra_where_clause -publish_status all -package_id [$wf package_id]]grading_dialog_setup (scripted, public)
<instance of xowf::test_item::Answer_manager> grading_dialog_setup \ examWfDefine the modal dialog and everything necessary for reusing this dialog for multiple occasions. This method registers the pop-up and dismiss handlers for JavaScript and returns the HTML markup of the modal dialog.
- Parameters:
- examWf (required)
- Returns:
- HTML block for the modal dialog
- Testcases:
- No testcase defined.
set url [$examWf pretty_link -query m=grade-single-item] # jquery-ui is just needed for draggable() ::template::add_body_script -src urn:ad:js:jquery-ui ::template::add_body_script -script [subst -novariables { function thumbnail_files_setup(element) { // to be called on the elements of class ".thumbnail-file" element.querySelectorAll('.thumbnail-file-text a.delete') .forEach(el => el.addEventListener('click', event => { // Get the "href" of the a.delete element // and call this actions in the background var href = event.currentTarget.getAttribute('href'); if (!href) { console.log(".thumbnail-file does not have a proper delete link"); return } var fileIcon = event.currentTarget.parentElement.parentElement; var request = new XMLHttpRequest(); request.open('GET', href, true); request.onload = function() { if (this.status >= 200 && this.status < 400) { // Success! fileIcon.parentNode.removeChild(fileIcon); } else { console.log('AJAX request returned bad return code: ' + this.status); } }; request.send(); event.preventDefault(); })); }; $(document).ready(function(){ document.querySelectorAll('.thumbnail-file').forEach(el => thumbnail_files_setup(el)); $('.modal-dialog').draggable(); $('.modal .confirm').on('click', function(ev) { // // Popdown: "submit" button of grading dialog was pressed. // var id = ev.currentTarget.dataset.id; var gradingBox = document.getElementById(id); var pointsInput = document.querySelector('#grading-points'); var helpBlock = document.querySelector('#grading-points-help-block'); var comment = document.querySelector('#grading-comment').value; var points = pointsInput.value; var pointsFormGroup = pointsInput.parentElement.parentElement; var percentage = ""; let hiddenCSSclass = '[::template::CSS class d-none]'; if (points != "") { // // Number validation // if (parseFloat(points) > parseFloat(pointsInput.max) || parseFloat(points) < parseFloat(pointsInput.min)){ if (parseFloat(points) > parseFloat(pointsInput.max)) { helpBlock.textContent = '[_ xowf.Value_max] ' + pointsInput.max; } else { helpBlock.textContent = '[_ xowf.Value_min] ' + pointsInput.min; } pointsFormGroup.classList.add('has-error'); helpBlock.classList.remove(hiddenCSSclass); ev.preventDefault(); return false; } else { pointsFormGroup.classList.remove('has-error'); helpBlock.classList.add(hiddenCSSclass); } var achievable = gradingBox.dataset.achievable; if (achievable != "") { percentage = "(" + (points*100.0/achievable).toFixed(2) + "%)"; } } else { pointsFormGroup.classList.remove('has-error'); helpBlock.classList.add(hiddenCSSclass); } document.querySelector('#' + id + ' .points').textContent = points; document.querySelector('#' + id + ' .percentage').textContent = percentage; document.querySelector('#' + id + ' .comment').textContent = comment; gradingBox.dataset.achieved = points; gradingBox.dataset.comment = comment; if (comment == "") { document.querySelector('#' + id + ' .feedback-label').classList.add(hiddenCSSclass); } else { document.querySelector('#' + id + ' .feedback-label').classList.remove(hiddenCSSclass); } // Copy the content of the thumbnail files wrapper from the dialog // to the main document and register the event handler. let thumbnailFilesWrapper = document.querySelector('#' + id + ' .thumbnail-files-wrapper'); if (!thumbnailFilesWrapper) { thumbnailFilesWrapper = document.createElement('div'); thumbnailFilesWrapper.className = 'thumbnail-files-wrapper'; document.querySelector('#' + id).appendChild(thumbnailFilesWrapper); } thumbnailFilesWrapper.innerHTML = document.querySelector('#thumbnail-files-wrapper').innerHTML; //document.querySelector('#' + id + ' .thumbnail-files-wrapper').innerHTML = // document.querySelector('#thumbnail-files-wrapper').innerHTML; gradingBox.querySelectorAll('.thumbnail-file').forEach(el => thumbnail_files_setup(el)); var user_id = gradingBox.dataset.user_id; var examGradingBox = document.getElementById('runtime-panel-' + user_id); var data = new FormData(); data.append('question_name', gradingBox.dataset.question_name); data.append('user_id', user_id); data.append('achieved', points); data.append('comment', comment); data.append('grading_scheme', examGradingBox.dataset.grading_scheme); data.append('achieved_points', examGradingBox.dataset.achieved_points); var xhttp = new XMLHttpRequest(); xhttp.open('POST', '[set url]', true); xhttp.onload = function () { if (this.readyState == 4) { if (this.status == 200) { var text = this.responseText; var span = document.querySelector('#runtime-panel-' + user_id + ' .achieved-points'); span.textContent = text; } else { console.log('sent NOT ok'); } } }; xhttp.send(data); return true; }); $('.modal-dialog form').keypress(function(e){ if(e.keyCode == 13) { e.preventDefault(); return false; } }); $('#grading-modal').on('shown.bs.modal', function (ev) { // // Popup of grading dialog. // Copy values from data attributes to input fields. // var gradingBox = ev.relatedTarget.parentElement; document.getElementById('grading-question-title').textContent = gradingBox.dataset.title; document.getElementById('grading-participant').textContent = gradingBox.dataset.full_name; var pointsInput = document.getElementById('grading-points'); pointsInput.value = gradingBox.dataset.achieved; pointsInput.max = gradingBox.dataset.achievable; document.getElementById('grading-comment').value = gradingBox.dataset.comment; //document.getElementById('drop-zone').dataset.link = gradingBox.dataset.link; var filesUpload = document.getElementById('js-upload-files'); filesUpload.dataset.file_name_prefix = gradingBox.dataset.question_name; filesUpload.dataset.url = gradingBox.dataset.link; filesUpload.dataset.disposition = "FileIconified"; //console.log("... URL " + filesUpload.dataset.url); var feedBackFiles = gradingBox.getElementsByClassName("thumbnail-files-wrapper")\[0\]; // // For legacy composite items, there is no "thumbnail-files-wrapper" // // console.log(feedBackFiles); document.getElementById('thumbnail-files-wrapper').innerHTML = (feedBackFiles ? feedBackFiles.innerHTML : ""); document.querySelectorAll('#grading-modal .thumbnail-file').forEach(el => thumbnail_files_setup(el)); // Tell confirm button to which grading box it belongs var confirmButton = document.querySelector('#grading-modal-confirm'); confirmButton.dataset.id = gradingBox.id; }); }); }] set uploader_link [::[$examWf package_id] make_link $examWf file-upload] set dropZone [::xowiki::BootstrapNavbarDropzone new -href $uploader_link -label #xowf.Feedback_files_dnd# -text "Text for SUBMIT label" -file_name_prefix "" -disposition File] set dropZoneHTML [$dropZone asHTML] #ns_log notice "dropZoneHTML=$dropZoneHTML" return [::xowiki::bootstrap::modal_dialog -id grading-modal -title "#xowf.Grading#: <span id='grading-participant'></span>" -subtitle "#xowf.question#: <span id='grading-question-title'></span>" -body [subst [ns_trim -delimiter | { |<form class="form-horizontal" role="form" action='#' method="post"> | <div class="form-group"> | <label for="grading-points" class="control-label col-sm-2">#xowf.Points#:</label> | <div class="col-sm-9"> | <input class="form-control" id="grading-points" placeholder="#xowf.Points#" | type="number" step="0.1" min="0"> | <span id="grading-points-help-block" class="help-block hidden"></span> | </div> | </div> | <div class="form-group"> | <label for="grading-comment" class="control-label col-sm-2">#xowf.feedback#:</label> | <div class="col-sm-9"> | <textarea lines="2" class="form-control" id="grading-comment" | placeholder="..."></textarea> | </div> | </div> |</form> |<div class="control-label">#xowf.Feedback_files#:</div> |<div id="thumbnail-files-wrapper"></div> |<ul class="dropZone">$dropZoneHTML</ul> }]] ]grading_scheme (scripted, public)
<instance of xowf::test_item::Answer_manager> grading_scheme \ -examWf examWf [ -grading grading ] \ [ -total_points total_points ]Return the grading scheme object based on the provided short name. In case the grading scheme belongs to the predefined grading schemes, the object can be directly loaded. When the name refers to a user-defined grading object, this might have to be loaded. We could consider some hints about the usefulness of the chosen grading scheme, E.g., when an exam has 40 points or less, rounding has the potential effect that a high percentage of the grade is just due to rounding. So, in such cases a non-rounding scheme should be preferred.
- Switches:
- -examWf (required, object)
- -grading (optional)
- -total_points (optional, defaults to
"100"
)- Returns:
- fully qualified grading scheme object
- Testcases:
- No testcase defined.
# # When not grading is provided, this muse be a legacy question. # if {$grading eq ""} { #set grading [expr {$total_points < 40 ? "round-none" : "round-points"}] set grading "none" ns_log notice "--- legacy grading scheme -> none" } set grading_scheme ::xowf::test_item::grading::$grading if {![nsf::is object $grading_scheme]} { # # Maybe we have to load this grading scheme... # #ns_log notice "grading_scheme_name load loaded yet: '$grading'" #::xo::show_stack ::xowf::test_item::grading::load_grading_schemes -package_id [$examWf package_id] -parent_id [$examWf parent_id] ns_log notice "--- grading schemes loaded" } if {![nsf::is object $grading_scheme]} { set grading_scheme ::xowf::test_item::grading::round-points ns_log notice "--- fallback to default grading scheme object" } #ns_log notice "USE grading_scheme $grading_scheme" return $grading_schemegrading_table (scripted, public)
<instance of xowf::test_item::Answer_manager> grading_table \ [ -csv csv ] grade_dictProduce HTML markup based on a dict with grades as keys and counts as values.
- Switches:
- -csv (optional)
- Parameters:
- grade_dict (required)
- Testcases:
- No testcase defined.
set gradingTable {<div class="grading-info"><div class="table-responsive"><table class="table grading">} append gradingTable "<thead><th class='text-right col-md-1'>#xowf.Grade#</th><th class='col-md-1 text-right'>#</th></thead>" "<tbody>\n" set nrGrades 0 foreach v [dict values $grade_dict] { incr nrGrades $v} set grades [lsort [dict keys $grade_dict]] foreach k $grades { set count [dict get $grade_dict $k] set countPercentage [format %.2f [expr {$count *100.0 / $nrGrades}]] append gradingTable <tr> [subst {<td class="text-right">$k</td><td class="text-right">$count</td>}] [subst {<td><div class="progress"><div class="progress-bar" style="width:$countPercentage%">$countPercentage%</div></td}] </tr>\n } append gradingTable "</tbody></table></div>\n" if {$csv ne "" } { append gradingTable "<pre>$csv</pre></div>\n" } if {[template::head::can_resolve_urn urn:ad:js:highcharts]} { # # The highcharts package is available # template::add_body_script -src urn:ad:js:highcharts set graphID pie-[incr ::__xotcl_highcharts_pie] append gradingTable "<div id='$graphID'></div>\n" set data "" foreach k $grades { set count [dict get $grade_dict $k] set countPercentage [format %.2f [expr {$count *100.0 / $nrGrades}]] lappend data [subst {{name:'$k', y: $countPercentage}}] } set gradeLabel [_ xowf.Grade] template::add_body_script -script [subst [ns_trim { Highcharts.chart('$graphID', { chart: {type: 'pie'}, plotOptions: {pie: {size: 200}, series: {dataLabels: {enabled: true, format: '$gradeLabel {point.name}: {point.y:.1f}%'} }}, title: {text: ''}, credits: {enabled: true }, series: \[{name: 'Percentage', data: \[ [join $data ,] \]}\] }); }]] } return $gradingTablelast_time_in_state (scripted, public)
<instance of xowf::test_item::Answer_manager> last_time_in_state \ -state state revision_setsLoops through revision sets and retrieves the latest date where state is equal the specified value.
- Switches:
- -state (required)
- Parameters:
- revision_sets (required)
- a list of ns_sets containing revision data. List is assumed to be sorted in descending creation_date order (as retrieved by get_revision_sets)
- Returns:
- a date
- Testcases:
- No testcase defined.
set result "" foreach ps $revision_sets { if {$state eq [ns_set get $ps state]} { set result [ns_set get $ps last_modified] } } return $resultlast_time_switched_to_state (scripted, public)
<instance of xowf::test_item::Answer_manager> last_time_switched_to_state \ -state state [ -before before ] revision_setsLoops through revision sets and retrieves the latest date where state is equal the specified value.
- Switches:
- -state (required)
- -before (optional)
- Parameters:
- revision_sets (required)
- a list of ns_sets containing revision data. List is assumed to be sorted in descending creation_date order (as retrieved by get_revision_sets)
- Returns:
- a date
- Testcases:
- No testcase defined.
set result "" set last_state "" foreach ps $revision_sets { if {$before ne ""} { set currentClock [clock scan [::xo::db::tcl_date [ns_set get $ps last_modified] tz]] if {$currentClock > $before} { break } } if {$last_state ne $state && $state eq [ns_set get $ps state]} { set result [ns_set get $ps last_modified] } set last_state [ns_set get $ps state] } return $resultmarked_results (scripted, public)
<instance of xowf::test_item::Answer_manager> marked_results \ [ -obj obj ] [ -wf wf ] form_infoReturn for every participant the individual results for an exam
- Switches:
- -obj (optional, object)
- -wf (optional, object)
- Parameters:
- form_info (required)
- Testcases:
- No testcase defined.
set form_field_objs [:answer_form_field_objs -wf $wf $form_info] set items [:get_wf_instances $wf] set results "" foreach i [$items children] { xo::cc eval_as_user -user_id [$i creation_user] { set participantResult [:participant_result -obj $obj $i $form_info $form_field_objs] } append results $participantResult \n } #ns_log notice "=== marked_results of [llength [$items children]] items => $results" return $resultsparticipants_table (scripted, public)
<instance of xowf::test_item::Answer_manager> participants_table \ [ -package_id package_id ] -items items \ [ -view_all_method view_all_method ] [ -state state ] wfThis method returns an HTML table containing a row for every participant with Name and short summary information. This table provides as well an interface for sending messages to this student.
- Switches:
- -package_id (optional, integer)
- -items (required, object)
- -view_all_method (optional, defaults to
"print-answers"
)- -state (optional, defaults to
"done"
)- Parameters:
- wf (required, object)
- Testcases:
- No testcase defined.
set form_field_objs {} lappend form_field_objs [$wf create_raw_form_field -name _online-exam-userName -spec text,label=#xowf.participant#] [$wf create_raw_form_field -name _online-exam-fullName -spec label,label=#acs-subsite.Name#,disableOutputEscaping=true] [$wf create_raw_form_field -name _state -spec text,label=#xowf.Status#] [$wf create_raw_form_field -name _online-exam-seconds -spec number,label=#xowf.Seconds#] [$wf create_raw_form_field -name _creation_date -spec date,label=#xowiki.Page-last_modified#] # # Take "orderby" from the query parameter. If not set, order by # the first field. # set orderby [::$package_id query_parameter orderby:token ""] if {$orderby eq "" && [llength $form_field_objs] > 0} { set orderby [[lindex $form_field_objs 0] name],asc } # # Create table widget. # set table_widget [::xowiki::TableWidget create_from_form_fields -package_id $package_id -form_field_objs $form_field_objs -type_map {_online-exam-seconds integer} -orderby $orderby] # # Extend properties of individual answers and add notification # dialogs. # set dialogs "" set user_list {} foreach p [$items children] { #foreach ff_obj $answer_form_field_objs { # $ff_obj object $p # set property [$ff_obj name] # $ff_obj value [$p property $property] #} # # Provide a notification dialog only before the student has # submitted her exam and the exam is published. # if {[$p state] ne "done" && [$wf state] eq "published"} { set dialog_info [::xowiki::includelet::personal-notification-messages modal_message_dialog -to_user_id [$p creation_user]] append dialogs [dict get $dialog_info dialog] \n set notification_dialog_button [dict get $dialog_info link] $p set online-exam-fullName "$notification_dialog_button [$p set online-exam-fullName]</a>" lappend user_list [$p creation_user] } # # Extend every answer with corresponding precomputed extra # "_online-exam-*" values to ease rendering: # set duration [:get_duration [$p get_revision_sets]] $p set_property -new 1 _online-exam-seconds [dict get $duration seconds] } ::xowiki::includelet::personal-notification-messages modal_message_dialog_register_submit -url [$wf pretty_link -query m=send-participant-message] set bulk_notification_HTML "" if {$state eq "done"} { set uc {tcl {[$p state] ne "done"}} } else { set uc {tcl {false}} if {[llength $user_list] > 0} { # # Provide bulk notification message dialog to send message to all users # set dialog_info [::xowiki::includelet::personal-notification-messages modal_message_dialog -to_user_id $user_list] append dialogs [dict get $dialog_info dialog] \n set notification_dialog_button [dict get $dialog_info link] set bulk_notification_HTML "<div class='bulk-personal-notification-message'>$notification_dialog_button #xowiki.Send_message_to# [llength $user_list] #xowf.Participants#</a></div>" } } # # Render table widget with extended properties. # set HTML [$table_widget render_page_items_as_table -package_id $package_id -items $items -form_field_objs $form_field_objs -csv true -uc $uc -view_field _online-exam-userName -view_filter_link [$wf pretty_link -query m=$view_all_method] {*}[expr {[info exists generate] ? [list -generate $generate] : ""}] -return_url [ad_return_url] -return_url_att local_return_url ] $table_widget destroy return $dialogs$HTML$bulk_notification_HTMLprevent_multiple_tabs (scripted, public)
<instance of xowf::test_item::Answer_manager> prevent_multiple_tabs \ [ -cookie_name cookie_name ]Prevent answering the same survey from multiple, concurrently open tabs.
- Switches:
- -cookie_name (optional, defaults to
"multiple_tabs"
)- Testcases:
- No testcase defined.
template::add_body_script -script [subst { var cookieLine = document.cookie.split('; ').find(row => row.startsWith('$cookie_name=')); var cookieValue = (cookieLine === undefined) ? 1 : parseInt(cookieLine.split('=')\[1\]) + 1; // console.log("cookie $cookie_name " + cookieValue); if (cookieValue > 1) { alert('Already open!'); window.open("about:blank", "_self").close(); } document.cookie = "$cookie_name=" + cookieValue; // console.log("START finished -> " + document.cookie); window.onunload = function () { var cookieLine = document.cookie.split('; ').find(row => row.startsWith('$cookie_name=')); var cookieValue = (cookieLine === undefined) ? 0 : parseInt(cookieLine.split('=')\[1\]) - 1; document.cookie = "$cookie_name=" + cookieValue; // console.log("UNLOAD finished -> " + document.cookie); }; }]recutil_create (scripted, public)
<instance of xowf::test_item::Answer_manager> recutil_create \ [ -exam_id exam_id ] [ -fn fn ] [ -clear ]Create recfile
- Switches:
- -exam_id (optional, integer)
- -fn (optional, defaults to
"answers.rec"
)- -clear (optional)
- See Also:
- Testcases:
- No testcase defined.
set export_dir $::acs::rootdir/log/exam-exports/$exam_id/ if {![file isdirectory $export_dir]} { file mkdir $export_dir } if {$clear && [file exists $export_dir$fn]} { file delete -- $export_dir$fn } # # If we have no recutils, create for the time being a stub # if {![nsf::is class ::xo::recutil]} { ns_log warning "no recutil class available" set r [::xotcl::Object new -proc ins args {;}] return $r } return [::xo::recutil new -file $export_dir$fn]render_answers (scripted, public)
<instance of xowf::test_item::Answer_manager> render_answers \ [ -as_student on|off ] \ [ -filter_submission_id filter_submission_id ] \ [ -creation_user creation_user ] [ -revision_id revision_id ] \ [ -filter_form_ids filter_form_ids ] [ -export on|off ] \ [ -orderby orderby ] [ -grading grading ] \ [ -with_grading_table on|off ] examWfReturn the answers in HTML format in a somewhat printer friendly way, e.g. as the exam protocol.
- Switches:
- -as_student (optional, boolean, defaults to
"false"
)- -filter_submission_id (optional, integer, accept empty)
- -creation_user (optional, integer, accept empty)
- -revision_id (optional, integer, accept empty)
- -filter_form_ids (optional, integer)
- -export (optional, boolean, defaults to
"false"
)- -orderby (optional, defaults to
"online-exam-userName"
)- -grading (optional)
- -with_grading_table (optional, boolean, defaults to
"false"
)- Parameters:
- examWf (required, object)
- Returns:
- dict containing "do_stream" and "HTML" ns_log notice "RENDER ANSWERS 0"
- Testcases:
- No testcase defined.
set combined_form_info [:QM combined_question_form $examWf] set autograde [dict get $combined_form_info autograde] set totalPoints [:QM total_points -max_items [$examWf property max_items ""] $combined_form_info] set withSignature [$examWf property signature 0] set examTitle [$examWf title] set ctx [::xowf::Context require $examWf] set results "" set wf [:get_answer_wf $examWf] if {$wf eq ""} { return [list do_stream 0 HTML ""] } if {$filter_form_ids ne "" && $filter_form_ids ni [dict get $combined_form_info question_objs]} { ns_log warning "inclass-exam: ignore invalid form_obj '$filter_form_ids';" "valid [dict get $combined_form_info question_objs]" set filter_form_ids "" } ns_log notice "--- grading '$grading'" set grading_scheme [:grading_scheme -examWf $examWf -grading $grading -total_points $totalPoints] #ns_log notice "--- grading_scheme $grading_scheme from grading '$grading'" set :grade_dict {} set :grade_csv "" set items [:submissions -creation_user $creation_user -filter_submission_id $filter_submission_id -revision_id $revision_id -wf $wf] # # In case we have many items to render (which might take a # while), use streaming mode. # set do_stream [expr {[llength [$items children]] > 100}] set HTML [:render_print_button] if {!$as_student} { # # When rendering for teachers, we offer the possibility for to # sort, filter and export submissions. # append HTML [:render_filter_bar -examWf $examWf -filter_form_ids $filter_form_ids -revision_id $revision_id -filter_submission_id $filter_submission_id -orderby $orderby] } ::xo::cc set_parameter template_file view-plain-master ::xo::cc set_parameter MenuBar 0 if {[llength $filter_form_ids] > 0} { # # Filter by questions. For the time being, we allow only a # single question, ... and we take the first ones. # append HTML "<h2>#xowf.question#: [ns_quotehtml [[lindex $filter_form_ids 0] title]]</h2>\n" set runtime_panel_view "" } elseif {$as_student} { # # Show the student his own submission # set userName [acs_user::get_element -user_id [ad_conn user_id] -element username] set fullName [::xo::get_user_name [ad_conn user_id]] set heading "$userName - $fullName" append HTML "<h2>#xowf.online-exam-review-protocol# - $heading</h2>\n" set runtime_panel_view "student" } else { # # Provide the full protocol (or a subset of it) # append HTML "<h2>#xowf.online-exam-protocol#</h2>\n" if {$filter_submission_id ne ""} { set runtime_panel_view "revision_overview" } else { set runtime_panel_view "default" } } append HTML [:grading_dialog_setup $examWf] #ns_log notice "RENDER ANSWERS 1" if {$do_stream} { # ns_log notice STREAM-[info level]-$::template::parse_level # # The following line is tricky: set on the parsing level the # title of and context of the page, since this is needed by # the streaming template. # uplevel #$::template::parse_level [subst {set title "$examTitle"; set context .}] ad_return_top_of_page [ad_parse_template -params [list context title] [template::streaming_template]] ns_write [subst { <div class=''main-content> <div class='xowiki-content' style='padding-left:15px;'> <h1>[ns_quotehtml $examTitle]</h1> [lang::util::localize $HTML] }] set HTML "" } if {$export} { set recutil [:recutil_create -clear -exam_id [$wf parent_id] -fn [expr {$filter_submission_id eq "" ? "all.rec" : "$filter_submission_id.rec"}] ] } else { set recutil "" } #ns_log notice "RENDER ANSWERS 2" # # Create zip file from file submissions # set create_zip_file [::xo::cc query_parameter create-file-submission-zip-file:boolean 0] if {$create_zip_file} { package req nx::zip [$examWf package_id] get_lang_and_name -name [$examWf set name] lang stripped_name if {[string equal [nx::zip::Archive info lookup parameters create name] -name]} { set zipFile [nx::zip::Archive new -name [ad_sanitize_filename $stripped_name]] } else { set zipFile [::nx::zip::Archive new] # # Post-register property, since it is not yet available in # this version of nx. # $zipFile object property name $zipFile configure -name [ad_sanitize_filename $stripped_name] } } else { set zipFile "" } #ns_log notice "RENDER ANSWERS 3 (submissions: [llength [$items children]])" set file_submission_exists 0 set form_objs_exam [:QM load_question_objs $examWf [$examWf property question]] set question_dict [:FL name_to_question_obj_dict $form_objs_exam] #ns_log notice "passed filter_form_ids <$filter_form_ids> form_objs_exam <$form_objs_exam>" # # Iterate over the items sorted by orderby. # $items orderby $orderby foreach submission [$items children] { set d [:render_submission=exam_protocol -submission $submission -wf $wf -examWf $examWf -exam_question_dict $question_dict -autograde $autograde -combined_form_info $combined_form_info -filter_submission_id $filter_submission_id -filter_form_ids $filter_form_ids -grading_scheme $grading_scheme -recutil $recutil -zipFile $zipFile -revision_id $revision_id -totalPoints $totalPoints -runtime_panel_view $runtime_panel_view -with_exam_heading [expr {!$as_student}] -with_signature $withSignature] set html [:dict_value $d HTML] #ns_log notice "RENDER ANSWERS setting result" dict set results [$submission set creation_user] [:dict_value $d results] if {$do_stream && $html ne ""} { ns_write [lang::util::localize $html] } else { append HTML $html } # # Check if we have found a file submission # if {!$file_submission_exists && !$export && [llength [:get_non_empty_file_formfields -submission $submission]] > 0 } { set file_submission_exists 1 } } #ns_log notice "RENDER ANSWERS 4" if {$export} { $recutil destroy } if {$with_grading_table && $autograde && $grading ne "none"} { append HTML <p>[:grading_table -csv ${:grade_csv} ${:grade_dict}]</p> # # The following lines are convenient for debugging # #set manual_gradings [$examWf property manual_gradings] #set manual_gradings [:get_exam_results -obj $examWf manual_gradings] #append HTML <pre>$manual_gradings</pre> #append HTML <pre>[:exam_results -manual_gradings $manual_gradings $results]</pre> } if {$create_zip_file} { $zipFile ns_returnZipFile [$zipFile cget -name].zip $zipFile destroy ad_script_abort } # # If we have already some file submission we are showing a link # for bulk-downloading the submissions # if {$file_submission_exists} { # # Avoid empty entries for query parameters # if {[llength $filter_form_ids] > 0} { set fos $filter_form_ids } foreach value {revision_id filter_submission_id} var {rid id} { if {[set $value] ne ""} { set $var [set $value] } } set href [$examWf pretty_link -query [export_vars { {m print-answers} {create-file-submission-zip-file 1} fos rid id }]] append HTML [ns_trim -delimiter | [subst { |<a href='[ns_quotehtml $href]'> |[::xowiki::bootstrap::icon -name download -CSSclass download-submissions] |#xowf.Download_file_submissions#</a> }]] } #ns_log notice "RENDER ANSWERS 5" # # Store statistics only in autograding cases, and only, when it # was a full evaluation of the exam. This has the advantage # that we do no have to partially update the statistics. These # are somewhat overly conservative assumptions for now, which # might be partially relaxed in the future. # if {$with_grading_table && !$as_student && $filter_submission_id eq "" && $creation_user eq "" && $revision_id eq "" } { set statistics {} set ia [$examWf instance_attributes] if {$autograde} { foreach var {__stats_success __stats_count} key {success count} { if {[$examWf exists $var]} { dict set statistics $key [$examWf set $var] $examWf unset $var } } :AM set_exam_results -obj $examWf statistics $statistics } :AM set_exam_results -obj $examWf results $results } return [list do_stream $do_stream HTML $HTML]render_answers_with_edit_history (scripted, public)
<instance of xowf::test_item::Answer_manager> render_answers_with_edit_history \ examWfAnalyze the student submissions an find situations, where input is "cleared" between revisions and return the HTML rendering. TODO: we should resolve this, move the exam protocol rendering (www-print-answers) also into the answer manager and make it configurable to provide this as an alternate item renderer. The current result is provided for all submission,s, but in general, this could be as well made available per question or per-student.
- Parameters:
- examWf (required, object)
- Returns:
- HTML
- Testcases:
- No testcase defined.
set wf [:get_answer_wf $examWf] if {$wf eq ""} { return "" } set submissions [:submissions -wf $wf] set HTML [:render_submissions=edit_history -examWf $examWf -submissions $submissions] return $HTMLresults_table (scripted, public)
<instance of xowf::test_item::Answer_manager> results_table \ [ -package_id package_id ] -items items \ [ -view_all_method view_all_method ] [ -with_answers on|off ] \ [ -state state ] [ -grading_scheme grading_scheme ] wfRender the results in format of a table and return HTML. Currently mostly deactivated (but potentially called by online-exam.wf and topic-assignment.wf).
- Switches:
- -package_id (optional, integer)
- -items (required, object)
- -view_all_method (optional, defaults to
"print-answers"
)- -with_answers (optional, boolean, defaults to
"true"
)- -state (optional, defaults to
"done"
)- -grading_scheme (optional, defaults to
"::xowf::test_item::grading::none"
)- Parameters:
- wf (required, object)
- Testcases:
- No testcase defined.
#set form_info [:combined_question_form -with_numbers $wf] set form_info [:QM combined_question_form $wf] set answer_form_field_objs [:answer_form_field_objs -wf $wf $form_info] set autograde [dict get $form_info autograde] #if {$autograde && [llength $answer_form_field_objs] > 10} { # set with_answers 0 #} set form_field_objs {} lappend form_field_objs [$wf create_raw_form_field -name _online-exam-userName -spec text,label=#xowf.participant#] if {$with_answers} { # # Create for every answer field a matching grading field # set ff_dict {} foreach answer_field_obj $answer_form_field_objs { #ns_log notice "LABEL [$answer_field_obj name] <[$answer_field_obj label]>" $answer_field_obj label [string trimright [$answer_field_obj name] _] $answer_field_obj mixin ::xowf::test_item::td_pretty_value set grading_field_obj [$wf create_raw_form_field -name [$answer_field_obj name].score -spec number,label=#xowf.Grading-Score#] lappend form_field_objs $answer_field_obj $grading_field_obj dict set ff_dict [$answer_field_obj name] $answer_field_obj dict set ff_dict [$grading_field_obj name] $grading_field_obj } } # if {0 && $autograde} { # lappend form_field_objs # [$wf create_raw_form_field # -name _online-exam-total-score # -spec number,label=#xowf.Total-Score#] # [$wf create_raw_form_field # -name _online-exam-grade # -spec number,label=#xowf.Grade#] # } lappend form_field_objs [$wf create_raw_form_field -name _online-exam-seconds -spec number,label=#xowf.Seconds#] [$wf create_raw_form_field -name _creation_date -spec date,label=#xowiki.Page-last_modified#] # # Check, if any of the answer form field objects is # randomized. If so, it is necessary to recreate these eagerly, # since the full object structure might be personalized. # set randomized_fields {} foreach ff_obj $answer_form_field_objs { if {[$ff_obj exists shuffle_kind] && [$ff_obj shuffle_kind] ne "none"} { lappend randomized_fields $ff_obj } } # # Take "orderby" from the query parameter. If not set, order by # the first field. # set orderby [::$package_id query_parameter orderby:token ""] if {$orderby eq "" && [llength $form_field_objs] > 0} { set orderby [[lindex $form_field_objs 0] name],asc } # # Create table widget. # set table_widget [::xowiki::TableWidget create_from_form_fields -package_id $package_id -form_field_objs $form_field_objs -orderby $orderby] # # Extend properties of every answer with corresponding ".score" # values. # foreach p [$items children] { # # If we have randomized fields, we have to # recreate/reinitialize these to get proper correction # markings for this user. It might be possible to optimize # this, when only a few fields are randomized. # if {[llength $randomized_fields] > 0} { #ns_log notice "WORK ON [$p creation_user] " :answer_form_field_objs -clear -wf $wf $form_info $wf form_field_flush_cache xo::cc eval_as_user -user_id [$p creation_user] { set answer_form_field_objs [:answer_form_field_objs -wf $wf $form_info] } } set total_score 0 set total_points 0 foreach ff_obj $answer_form_field_objs { $ff_obj object $p set property [$ff_obj name] $ff_obj value [$p property $property] $ff_obj set_feedback 3 #ns_log notice "[$p creation_user] [$ff_obj name] [$p property $property] -> [$ff_obj set evaluated_answer_result]" set r [expr {[$ff_obj exists grading_score] ? [$ff_obj set grading_score] : ""}] # # In case, we have a grading score, which is not starred, we # can compute points from this. # if {$r ne "" && ![regexp {[*]$} $r]} { # # Add exercise score weighted to the total score to # compute points. # if {[$ff_obj exists test_item_points]} { #ns_log notice "[$ff_obj name]: grading_score <$r>, test_item_points <[$ff_obj set test_item_points]>" set minutes [$ff_obj set test_item_points] set total_score [expr {$total_score + ($minutes * [$ff_obj set grading_score])}] set total_points [expr {$total_points + $minutes}] } #ns_log notice "==== [$ff_obj name] grading_score => $r" } else { set r [expr {[$ff_obj set evaluated_answer_result] eq "correct" ? 100.0 : 0.0}]* #ns_log notice [$ff_obj serialize] } $p set_property -new 1 $property.score $r } set duration [:get_duration [$p get_revision_sets]] $p set_property -new 1 _online-exam-seconds [dict get $duration seconds] # if {0 && $autograde && $total_points > 0} { # set final_score [expr {$total_score/$total_points}] # $p set_property -new 1 _online-exam-total-score $final_score # # set d [list achievedPoints $total_score achievablePoints $total_points totalPoints $total_points] # set grade [$grading_scheme grade -achieved_points $d] # dict incr grade_count $grade # $p set_property -new 1 _online-exam-grade $grade # } } if {$state eq "done"} { set uc {tcl {[$p state] ne "done"}} } else { set uc {tcl {false}} } # # Render table widget with extended properties. # set HTML [$table_widget render_page_items_as_table -package_id $package_id -items $items -form_field_objs $form_field_objs -csv true -uc $uc -view_field _online-exam-userName -view_filter_link [$wf pretty_link -query m=$view_all_method] {*}[expr {[info exists generate] ? [list -generate $generate] : ""}] -return_url [ad_return_url] -return_url_att local_return_url ] $table_widget destroy if {0 && $autograde} { set gradingTable {<div class="table-responsive"><table class="table">} append gradingTable "<thead><th class='text-right col-md-1'>#xowf.Grade#</th><th class='col-md-1 text-right'>#</th></thead>" "<tbody>\n" set nrGrades 0 foreach v [dict values $grade_count] { incr nrGrades $v} foreach k [lsort [dict keys $grade_count]] { set count [dict get $grade_count $k] set countPercentage [expr {$count*100.0/$nrGrades}] append gradingTable <tr> [subst {<td class="text-right">$k</td><td class="text-right">$count</td>}] [subst {<td><div class="progress"><div class="progress-bar" style="width:$countPercentage%">$countPercentage%</div></td}] </tr>\n } append gradingTable "</tbody></table></div>\n" append HTML <p>$gradingTable</p> } return $HTMLrevisions_up_to (scripted, public)
<instance of xowf::test_item::Answer_manager> revisions_up_to \ revision_sets revision_idReturn the revisions of the provided revision set up the provided revision_id. If this revision_id does not exist, return the full set.
- Parameters:
- revision_sets (required)
- revision_id (required)
- Testcases:
- No testcase defined.
set result "" set stop 0 return [lmap s $revision_sets { if {$stop} break set stop [expr {[ns_set get $s revision_id] eq $revision_id}] set s }]runtime_panel (scripted, public)
<instance of xowf::test_item::Answer_manager> runtime_panel \ [ -revision_id revision_id ] [ -view view ] \ [ -grading_info grading_info ] answerObjReturn statistics for the provided object in the form of HTML: - minimal statistics: when view default - statistics with clickable revisions: when view = revision_overview - per-revision statistics: when view = revision_overview and revision_id is provided
- Switches:
- -revision_id (optional)
- -view (optional, defaults to
"default"
)- -grading_info (optional)
- Parameters:
- answerObj (required, object)
- Returns:
- HTML block
- Testcases:
- No testcase defined.
set revision_sets [$answerObj get_revision_sets] set parent_revision_sets [[$answerObj parent_id] get_revision_sets] set item_id [$answerObj item_id] set live_revision_id [xo::dc get_value -prepare integer live_revision_id { select live_revision from cr_items where item_id = :item_id }] set current_question [expr {[dict get [$answerObj instance_attributes] position] + 1}] set page_info "#xowf.question#: $current_question" if {$view eq "default"} { set url [ad_return_url]&id=$item_id set revisionDetails "#xowf.nr_changes#: <a href='[ns_quotehtml $url]'>[llength $revision_sets]</a><br>" } elseif {$view eq "student"} { set revisionDetails "" } elseif {$view eq "revision_overview"} { set displayed_revision_info "" set live_revision_info "" set make_live_info "" set baseUrl [ns_conn url] set filtered_revision_sets [:revisions_up_to $revision_sets $revision_id] set c 0 foreach s $revision_sets { set rid [ns_set get $s revision_id] incr c if {$rid == $live_revision_id} { set liveCSSclass "live" set live_revision_info "#xowf.Live_revision#: $c" } else { set liveCSSclass "other" } set revision_url $baseUrl?[::xo::update_query [ns_conn query] rid $rid] if {$rid == [$answerObj revision_id]} { set suffix "*" set displayed_revision_info "#xowf.Displayed_revision#: $c" if {$rid ne $live_revision_id} { set query [::xo::update_query [ns_conn query] m make-live-revision] set query [::xo::update_query $query revision_id $rid] set query [::xo::update_query $query local_return_url [ad_return_url]] set live_revision_link $baseUrl?$query set make_live_info [subst { <a class="button" href="[ns_quotehtml $live_revision_link]">#xowf.Make_live_revision#</a> }] lappend revision_list "<span class='current'>$c</span>" } else { lappend revision_list "<span class='$liveCSSclass'>$c</span>" } } else { lappend revision_list [subst { <a class="$liveCSSclass" title="#xowf.Goto_this_revision#" href="[ns_quotehtml $revision_url]">$c</a> }] } } set revision_sets $filtered_revision_sets set revisionDetails [subst {#xowiki.revisions#: [join $revision_list {, }] <div class="revision-details right">$displayed_revision_info<br>$live_revision_info<br> $make_live_info </div> <br> }] } if {$revision_id eq ""} { set revision_sets [:revisions_up_to $revision_sets $live_revision_id] } set toClock [clock scan [::xo::db::tcl_date [ns_set get [lindex $revision_sets end] last_modified] tz]] set last_published [:last_time_switched_to_state $parent_revision_sets -state published -before $toClock] #ns_log notice "LAST PUBLISHED $last_published" set duration [:get_duration -exam_published_time $last_published $revision_sets] set state [$answerObj state] if {$state eq "done"} { set submission_info "#xowf.submitted#" } else { set submission_info "#xowf.not_submitted# ($page_info)" } if {[dict exists $duration examPublished]} { set publishedInfo "#xowf.Exam_published#: <span class='data'>[dict get $duration examPublished]</span><br>" set extraDurationInfo " - #xowf.since_published#: [dict get $duration examPublishedDuration]" } else { set publishedInfo "" set extraDurationInfo "" } if {$view eq "student"} { set IPinfo "" set statusInfo "" set extraDurationInfo "" set publishedInfo "" } else { set IPinfo [subst {IP: <span class="data">[:get_IPs $revision_sets]</span>}] set statusInfo "#xowf.Status#: <span class='data'>$submission_info</span><br>" } if {$grading_info ne ""} { set achievedPointsInfo [subst { #xowf.Achieved_points#: <span class='data achieved-points'>$grading_info</span><br> }] } else { set achievedPointsInfo "" } set HTML [subst { $publishedInfo $revisionDetails $statusInfo #xowf.Duration#: <span class="data">[dict get $duration from] - [dict get $duration to] ([dict get $duration duration]$extraDurationInfo)</span><br> $achievedPointsInfo $IPinfo }] return $HTMLset_exam_results (scripted, public)
<instance of xowf::test_item::Answer_manager> set_exam_results \ -obj obj property valuens_log notice "SES '$property' bytes [string length $value]"
- Switches:
- -obj (required, object)
- Parameters:
- property (required)
- value (required)
- Testcases:
- No testcase defined.
set p [$obj childpage -name en:result -form inclass-exam-statistics.wf] set instance_attributes [$p instance_attributes] dict set instance_attributes $property $value $p update_attribute_from_slot [$p find_slot instance_attributes] ${instance_attributes} # # cleanup of legacy values # set instance_attributes [$obj instance_attributes] foreach property_name [list $property __$property] { if {[dict exists $instance_attributes $property_name]} { ns_log notice "SES set_exam_results:" "clearing values from earlier releases for '$property_name'" "was <[dict get $instance_attributes $property_name]>" dict unset instance_attributes $property_name $obj set instance_attributes $instance_attributes #ns_log notice "FINAL IA <$instance_attributes> for item_id [$obj item_id]" "revision_id [$obj revision_id]" $obj update_attribute_from_slot [$obj find_slot instance_attributes] $instance_attributes ::xo::xotcl_object_cache flush [$obj item_id] ::xo::xotcl_object_cache flush [$obj revision_id] } }state_periods (scripted, public)
<instance of xowf::test_item::Answer_manager> state_periods \ -state state revision_setsReturn for the provided revision_sets the time ranges the workflow was in the provided state.
- Switches:
- -state (required)
- Parameters:
- revision_sets (required)
- Testcases:
- No testcase defined.
set periods "" set from "" set last_from "" set until "" foreach ps $revision_sets { set current_state [ns_set get $ps state] if {$state eq $current_state} { if {$until ne ""} { lappend periods [:pretty_period $last_from $until] } set from [ns_set get $ps creation_date] set until "" } elseif {$until eq "" && $current_state ne $state && $from ne ""} { set until [ns_set get $ps last_modified] set last_from $from set from "" } } if {$until ne ""} { lappend periods [:pretty_period $last_from $until] } elseif {$from ne ""} { lappend periods [:pretty_period $from ""] } #ns_log notice "state_periods $state <$from> <$last_from> <$until> <$periods>" return $periodsstudent_submissions_exist (scripted, public)
<instance of xowf::test_item::Answer_manager> student_submissions_exist \ wfReturns 1 if there are student submissions. The method returns already true, when a student has started to work on this exam. This method could be optimized if necessary via caching the wf_instances or a more specific database query.
- Parameters:
- wf (required, object)
- Testcases:
- No testcase defined.
set items [:get_wf_instances $wf] foreach i [$items children] { if {[$i property try_out_mode] ne "1"} { #ns_log notice "==================== student_submissions_exist 1" return 1 } } #ns_log notice "==================== student_submissions_exist 0" return 0time_window_setup (scripted, public)
<instance of xowf::test_item::Answer_manager> time_window_setup \ -time_window time_window parentObjCheck the provided time_window values, adjust it if necessary, and make sure, according atjobs are provided. This method was made public, since there configuration window update in inclass-exam.wf requires this for the update via update_attribute_from_slot. Probably, we should move the core of this function to this file, and make it protected again.
- Switches:
- -time_window (required)
- Parameters:
- parentObj (required, object)
- Testcases:
- No testcase defined.
set dtstart [dict get $time_window time_window.dtstart] set dtend [dict get $time_window time_window.dtend] # # Delete previously scheduled atjobs. This needs to happen in # any case, because people may have removed the time window # altogether. # ns_log notice "#### deleting old scheduled atjob" :delete_scheduled_atjobs $parentObj if {$dtstart ne ""} { set total_minutes [question_manager total_minutes_for_exam -manager $parentObj] ns_log notice "#### create_workflows: atjobs for time_window <$time_window> total-mins $total_minutes" set start_clock [clock scan $dtstart -format %Y-%m-%dT%H:%M] if {$dtend eq ""} { # # No end given. Set it to start + exam time + 5 minutes. # The value of "total_minutes" might contain fractions of a # minute, so make sure that the end_clock is an integer as # needed by "clock format", set end_clock [expr {int($start_clock + ($total_minutes + 5) * 60)}] set new_dtend [clock format $end_clock -format %H:%M] ns_log notice "#### no dtend given. set it from $dtend to $new_dtend" } else { set end_date [clock format $start_clock -format %Y-%m-%d]T$dtend set end_clock [clock scan $end_date -format %Y-%m-%dT%H:%M] if {($end_clock - $start_clock) < ($total_minutes * 60)} { # # The specified end time is too early. Set it to start + # exam time + 5 minutes. # set end_clock [expr {int($start_clock + ($total_minutes + 5)*60)}] set new_dtend [clock format $end_clock -format %H:%M] ns_log notice "#### dtend is too early. Move it from $dtend to $new_dtend" } else { set new_dtend $dtend } } if {$new_dtend ne $dtend} { ns_log notice "#### create_workflows: must change dtend from <$dtend> to <$new_dtend>" set ia [$parentObj instance_attributes] dict set time_window time_window.dtend $new_dtend dict set ia time_window $time_window #ns_log notice "SAVE updated ia <${:instance_attributes}>" $parentObj update_attribute_from_slot [$parentObj find_slot instance_attributes] $ia } # # Schedule new atjobs # ns_log notice "#### scheduling atjobs" $parentObj schedule_action -time [clock format $start_clock -format "%Y-%m-%d %H:%M:%S"] -action publish $parentObj schedule_action -time [clock format $end_clock -format "%Y-%m-%d %H:%M:%S"] -action unpublish }waiting_room_message (scripted, public)
<instance of xowf::test_item::Answer_manager> waiting_room_message \ objRenders the waiting room message, including the JavaScript reacting to actions from the backend.
- Parameters:
- obj (required, object)
- Testcases:
- No testcase defined.
set message [::xowiki::bootstrap::card -title #xowf.Waiting_Room# -body [subst { <p>[_ xowf.waiting_for_exam [list title "[$obj title]"]] <p><adp:icon name='clock'> <span id='waiting-msg'></span></p> #xowf.waiting_redirect# }]] set url [$obj pretty_link -query m=poll-open] template::add_body_script -script [subst -nocommands { (function poll() { setTimeout(function() { var xhttp = new XMLHttpRequest(); xhttp.open("GET", '$url', true); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var data = JSON.parse(xhttp.response); console.log(data); console.log(data["action"]); console.log(data["msg"]); if (data["action"] == "msg") { var el = document.querySelector('#waiting-msg'); el.innerHTML = data["msg"]; poll(); } else if (data["action"] == "redirect") { window.location.href = data["url"]; } else { console.log("something else"); } } }; xhttp.send(); }, 1000); })(); }] return $message
- Methods: All Methods Documented Methods Hide Methods
- Source: Display Source Hide Source
- Variables: Show Variables Hide Variables