Index: openacs-4/packages/xowf/tcl/test-item-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/tcl/test-item-procs.tcl,v diff -u -r1.7.2.3 -r1.7.2.4 --- openacs-4/packages/xowf/tcl/test-item-procs.tcl 4 Jul 2019 18:01:25 -0000 1.7.2.3 +++ openacs-4/packages/xowf/tcl/test-item-procs.tcl 15 Oct 2019 21:24:26 -0000 1.7.2.4 @@ -17,29 +17,58 @@ next } -} -namespace eval ::xowiki::formfield { + ########################################################### + # + # ::xowiki::formfield::TestItemField + # + ########################################################### + Class create TestItemField -superclass FormGeneratorField -parameter { + {feedback_level full} + {auto_correct:boolean false} + } -ad_doc { + Abstract class for defining common attributes for all Test Item + fields. + + @param feedback_level "full", or "none" + @param auto_correct boolean to let user add auto correction fields + } + TestItemField set abstract 1 + + ########################################################### # # ::xowiki::formfield::test_item # ########################################################### - Class create test_item -superclass FormGeneratorField -parameter { + Class create test_item -superclass TestItemField -parameter { {question_type mc} {nr_choices 5} - {feedback_level full} + {grading exact} + } -ad_doc { + + Wrapper for complex test items, containing specification for + minutes, grading scheme, feedback levels, handling different types + of questions ("interactions" in the terminology of QTI). When such + a question is saved, a HTML form is generated, which is used as a + question. + + @param feedback_level "full", or "none" + @param grading one of "exact", "partial", or "none" + @param nr_choices number of choices + @param question_type "mc", "sc", "ot", or "te" } - + # # provide a default setting for xinha JavaScript for test-items # - test_item set xinha(javascript) [::xowiki::formfield::FormField fc_encode { - xinha_config.toolbar = [ - ['popupeditor', 'bold','italic','createlink','insertimage','separator'], - ['killword','removeformat','htmlmode'] - ]; + test_item set xinha(javascript) [::xowiki::formfield::FormField fc_encode { + xinha_config.toolbar = [ + ['popupeditor', 'bold','italic','createlink','insertimage','separator'], + ['killword','removeformat','htmlmode'] + ]; }] + test_item set richtextWidget {richtext,editor=ckeditor4,ck_package=standard,extraPlugins=} test_item instproc feed_back_definition {auto_correct} { # @@ -48,90 +77,101 @@ # what's wrong, we can't provide different feedback for right or # wrong. # - :instvar inplace feedback_level - if {$feedback_level eq "none"} { + if {${:feedback_level} eq "none"} { return "" } - set widget "richtext,editor=ckeditor4,height=150px" + set widget [test_item set richtextWidget] if {$auto_correct} { return [subst { - {feedback_correct {$widget,label=#xowf.feedback_correct#}} - {feedback_incorrect {$widget,label=#xowf.feedback_incorrect#}} + {feedback_correct {$widget,height=150px,label=#xowf.feedback_correct#}} + {feedback_incorrect {$widget,height=150px,label=#xowf.feedback_incorrect#}} }] } return [subst { - {feedback {$widget,label=#xowf.feedback#}} + {feedback {$widget,label=Korrekturhinweis}} }] } # - # test_item is the wrapper for interaction to be used in + # "test_item" is the wrapper for interaction to be used in # evaluations. Different wrapper can be defined in a similar way for # questionairs, which might need less input fields. # test_item instproc initialize {} { - if {${:__state} ne "after_specs"} return - :instvar inplace feedback_level + if {${:__state} ne "after_specs"} { + return + } set options "" # # Provide some settings for name short-cuts # - switch -- [:question_type] { + switch -- ${:question_type} { mc { # we should support as well: minChoices, maxChoices, shuffle set interaction_class mc_interaction set options nr_choices=[:nr_choices] + set auto_correct true } sc { # we should support as well: minChoices, maxChoices, shuffle set interaction_class mc_interaction set options nr_choices=[:nr_choices],multiple=false + set auto_correct true } - ot { set interaction_class text_interaction } - default {error "unknown question type: [:question_type]"} + ot { + set interaction_class text_interaction + set auto_correct ${:auto_correct} + } + te { + set interaction_class text_entry_interaction + #set options nr_choices=[:nr_choices] + set auto_correct ${:auto_correct} + } + default {error "unknown question type: ${:question_type}"} } - - set auto_correct [expr {[$interaction_class exists auto_correct] && - [$interaction_class set auto_correct] == false ? 0 : 1}] - - # For the time being, we set inplace to false, otherwise we can't - # currently edit empty fields - set inplace true - + :log test_item-auto_correct=$auto_correct # - # handle feedback_level + # Handle feedback_level. # # The object might be a form, just use the property, if we are on # a FormPage. + # if {[${:object} istype ::xowiki::FormPage]} { set feedback_level_property [${:object} property feedback_level] if {$feedback_level_property ne ""} { - set feedback_level $feedback_level_property + set :feedback_level $feedback_level_property } } - # + if {${:grading} ne "none"} { + if {${:grading} ni {exact partial}} { + error "invalid grading '$grading'; valid are 'exact' or 'partial'" + } + set options "{exact exact} {partial partial}" + set gradingSpec [subst {grading {select,options=$options,default=${:grading},label=#xowf.Grading-Schema#}}] + } else { + set gradingSpec "" + } + :create_components [subst { - {minutes numeric,size=2,label=#xowf.Minutes#} - {grading {select,options={exact exact} {partial partial},default=exact,label=#xowf.Grading-Schema#}} - {interaction {$interaction_class,$options,feedback_level=$feedback_level,inplace=$inplace}} + {minutes number,min=1,default=2,label=#xowf.Minutes#} + $gradingSpec + {interaction {$interaction_class,$options,feedback_level=${:feedback_level},auto_correct=${:auto_correct}}} [:feed_back_definition $auto_correct] }] set :__initialized 1 } - } + namespace eval ::xowiki::formfield { ########################################################### # # ::xowiki::formfield::mc_interaction # ########################################################### - Class create mc_interaction -superclass FormGeneratorField -parameter { - {feedback_level full} - {inplace false} + Class create mc_interaction -superclass TestItemField -parameter { {shuffle false} {nr_choices 5} {multiple true} @@ -158,7 +198,6 @@ mc_interaction instproc initialize {} { if {${:__state} ne "after_specs"} return test_item instvar {xinha(javascript) javascript} - :instvar feedback_level inplace input_field_names nr_choices # # build choices # @@ -169,9 +208,10 @@ # # create component structure # + set widget [test_item set richtextWidget] :create_components [subst { - {text {richtext,required,height=150px,editor=ckeditor4,label=#xowf.exercise-text#}} - {mc {mc_choice,feedback_level=$feedback_level,label=#xowf.alternative#,multiple=[:multiple],repeat=1..$nr_choices}} + {text {$widget,required,height=150px,label=#xowf.exercise-text#}} + {mc {mc_choice,feedback_level=${:feedback_level},label=#xowf.alternative#,multiple=[:multiple],repeat=1..${:nr_choices}}} }] set :__initialized 1 } @@ -181,16 +221,16 @@ # Build a form from the components of the exercise on the fly. # Actually, this methods computes the properties "form" and # "form_constraints" based on the components of this form field. - # - set form "
\n\n" + # + set form "\n
\n" set fc "@categories:off @cr_fields:hidden\n" set intro_text [:get_named_sub_component_value text] append form "\n" #:msg " input_field_names=${:input_field_names}" set mc [:get_named_sub_component_value mc] ns_log notice "MC <$mc>" - + if {![:multiple]} { set correct_field_name [:get_named_sub_component_value correct] } @@ -199,10 +239,6 @@ set input_field_names {} foreach {name .} $mc {lappend input_field_names $name} - #mc_exercise.mc.0 {mc_exercise.mc.0.text {} mc_exercise.mc.0.correct t} - #mc_exercise.mc.1 {mc_exercise.mc.1.text a mc_exercise.mc.1.correct t} - #ns_log notice input_field_names=$input_field_names - # don't iterate over the template field foreach {input_field_name data} [lrange $mc 2 end] { foreach f {text correct feedback_correct feedback_incorrect} { @@ -224,13 +260,16 @@ if {[:multiple]} { set correct $value(correct) append form \ - "\n" \ + "\n" \ "\n" } else { #:msg $correct_field_name,${:name},$input_field_name set correct [expr {"${:name}.$input_field_name" eq $correct_field_name}] append form \ - "\n" \ + "\n" \ "\n" } #ns_log notice "$input_field_name [array get value] corr=$correct" @@ -255,7 +294,7 @@ regexp {[.]([^.]+)$} $correct_field_name _ correct_field_value lappend fc "radio:text,answer=$correct_field_value" } - append form "
$intro_text
" \ + "" \ + "
" \ + "
\n" + append form "\n" #ns_log notice FORM=$form #ns_log notice FC=$fc ${:object} set_property -new 1 form $form @@ -272,9 +311,7 @@ # ########################################################### - Class create mc_choice -superclass FormGeneratorField -parameter { - {feedback_level full} - {inplace true} + Class create mc_choice -superclass TestItemField -parameter { {multiple true} } @@ -295,21 +332,23 @@ } else { set feedback_fields "" } + + set widget [test_item set richtextWidget] if {[:multiple]} { - # We are in a multiple choice item; provide for editing a radio + # We are in a multiple-choice item; provide for editing a radio # group per alternative. :create_components [subst { - {text {richtext,editor=ckeditor4,$text_config}} + {text {$widget,$text_config}} {correct {boolean,horizontal=true,label=#xowf.correct#}} $feedback_fields }] } else { - # We are in a single choice item; provide for editing a single + # We are in a single-choice item; provide for editing a single # radio group spanning all entries. Use as name for grouping # the form-field name minus the last segment. regsub -all {[.][^.]+$} ${:name} "" groupname :create_components [subst { - {text {richtext,editor=ckeditor4,$text_config}} + {text {$widget,$text_config}} {correct {radio,label=#xowf.correct#,forced_name=$groupname.correct,options={"" ${:name}}}} $feedback_fields }] @@ -325,24 +364,29 @@ # ########################################################### - Class create text_interaction -superclass FormGeneratorField -parameter { - {feedback_level full} - {inplace true} + Class create text_interaction -superclass TestItemField -parameter { } - text_interaction set auto_correct false + #text_interaction set auto_correct false text_interaction instproc initialize {} { if {${:__state} ne "after_specs"} return test_item instvar {xinha(javascript) javascript} - :instvar feedback_level inplace input_field_names # - # create component structure + # Create component structure. # - # "inplace" is ignored + set widget [test_item set richtextWidget] + + if {${:auto_correct}} { + set autoCorrectSpec {{correct_when {text,label=#xowf.correct_when#}}} + } else { + set autoCorrectSpec "" + } + :create_components [subst { - {text {richtext,required,editor=ckeditor4,height=150px,label=#xowf.exercise-text#,plugins=OacsFs}} - {lines {numeric,default=10,size=3,label=#xowf.lines#}} - {columns {numeric,default=60,size=3,label=#xowf.columns#}} + {text {$widget,label=#xowf.exercise-text#,plugins=OacsFs}} + {lines {number,min=1,default=10,label=#xowf.answer_lines#}} + {columns {number,min=1,max=80,default=60,label=#xowf.answer_columns#}} + $autoCorrectSpec }] set :__initialized 1 } @@ -351,51 +395,175 @@ set intro_text [:get_named_sub_component_value text] set lines [:get_named_sub_component_value lines] set columns [:get_named_sub_component_value columns] + + if {${:auto_correct}} { + set correct_when [:get_named_sub_component_value correct_when] + set correct_when [::xowiki::formfield::FormField fc_encode correct_when=$correct_when], + #ns_log notice "correct_when <$correct_when>" + + } else { + set correct_when "" + } + append form \ "
\n" \ "
$intro_text
\n" \ "@answer@\n" \ "
\n" -# "\n" append fc \ "@categories:off @cr_fields:hidden\n" \ - "answer:textarea,label=Answer" + "{answer:textarea,rows=$lines,cols=$columns,$correct_when,label=Answer}" + + #ns_log notice "text_interaction $form\n$fc" ${:object} set_property -new 1 form $form ${:object} set_property -new 1 form_constraints $fc set anon_instances true ;# TODO make me configurable ${:object} set_property -new 1 anon_instances $anon_instances - ${:object} set_property -new 1 auto_correct [[self class] set auto_correct] + ${:object} set_property -new 1 auto_correct ${:auto_correct} ${:object} set_property -new 1 has_solution false } } namespace eval ::xowiki::formfield { ########################################################### # + # ::xowiki::formfield::text_entry_interaction + # + ########################################################### + + Class create text_entry_interaction -superclass TestItemField -parameter { + } + + text_entry_interaction instproc initialize {} { + if {${:__state} ne "after_specs"} return + test_item instvar {xinha(javascript) javascript} + # + # Create component structure. + # + set widget [test_item set richtextWidget] + ns_log notice "[self] [:info class] auto_correct=${:auto_correct}" + + :create_components [subst { + {text {$widget,label=#xowf.exercise-text#,plugins=OacsFs}} + {answer {text_entry_field,repeat=1..5}} + }] + set :__initialized 1 + } + + text_entry_interaction instproc convert_to_internal {} { + set intro_text [:get_named_sub_component_value text] + + #:msg " input_field_names=${:input_field_names}" + set answerFields [:get_named_sub_component_value answer] + ns_log notice "answerFields <$answerFields>" + + set count 0 + set list "\n" + append form \ + "
\n" \ + "
$intro_text
\n" \ + "$list" \n \ + "
\n" + lappend fc @categories:off @cr_fields:hidden + + ns_log notice "text_entry_interaction $form\n$fc" + ${:object} set_property -new 1 form $form + ${:object} set_property -new 1 form_constraints $fc + set anon_instances true ;# TODO make me configurable + ${:object} set_property -new 1 anon_instances $anon_instances + ${:object} set_property -new 1 auto_correct ${:auto_correct} + ${:object} set_property -new 1 has_solution false + } + + # + # ::xowiki::formfield::text_entry_field + # + Class create text_entry_field -superclass TestItemField -parameter { + } + + text_entry_field instproc initialize {} { + if {${:__state} ne "after_specs"} return + # + # Create component structure. + # + set widget [test_item set richtextWidget] + + # + # Get auto_correct from the interaction (passing "auto_correct=" + # via form constrain would requires to extend the RepeatContainer, + # otherwise the attribute is rejected). + # + set p [:info parent] + while {1} { + if {![$p istype ::xowiki::formfield::FormField]} break + if {![$p istype ::xowiki::formfield::text_entry_interaction]} { + set p [$p info parent] + continue + } + set :auto_correct [$p set auto_correct] + break + } + #:log "[:name] auto_correct ${:auto_correct}" + + if {${:auto_correct}} { + set autoCorrectSpec {{correct_when {text,label=#xowf.correct_when#}}} + } else { + set autoCorrectSpec "" + } + :msg autoCorrectSpec=$autoCorrectSpec + :create_components [subst { + {text {$widget,height=100px,label=Teilaufgabe,plugins=OacsFs}} + $autoCorrectSpec + }] + set :__initialized 1 + } + +} + + +namespace eval ::xowiki::formfield { + ########################################################### + # # ::xowiki::formfield::upload_interaction # ########################################################### - Class create upload_interaction -superclass FormGeneratorField -parameter { - {feedback_level full} + Class create upload_interaction -superclass TestItemField -parameter { } upload_interaction set auto_correct false upload_interaction instproc initialize {} { - if {${:__state} ne "after_specs"} return + if {${:__state} ne "after_specs"} { + return + } + set widget [test_item set richtextWidget] :create_components [subst { - {text {richtext,required,editor=ckeditor4,height=150px,label=#xowf.exercise-text#,plugins=OacsFs}} + {text {$widget,height=150px,label=#xowf.exercise-text#,plugins=OacsFs}} }] set :__initialized 1 } upload_interaction instproc convert_to_internal {} { set intro_text [:get_named_sub_component_value text] append form \ - "
\n" \ + "\n" \ "
$intro_text
\n" \ "@answer@" \ - "
\n" + "\n" append fc \ "@categories:off @cr_fields:hidden\n" \ "answer:file" @@ -433,7 +601,7 @@ # the form constraints. We use the item-id contained pages as a the # prefix for the form-fields. This method must be most likely # extended for other question types. - # + # set form "
\n" set fc "@categories:off @cr_fields:hidden\n" set intro_text [${:object} property _text] @@ -476,7 +644,7 @@ lappend alt_values $alt_value } } - # We have to drop the toplevel of the included form + # We have to drop the toplevel of the included form foreach n [$root childNodes] {append form [$n asHTML]} append form "\n" #