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.6 -r1.7.2.7 --- openacs-4/packages/xowf/tcl/test-item-procs.tcl 18 Oct 2019 08:35:52 -0000 1.7.2.6 +++ openacs-4/packages/xowf/tcl/test-item-procs.tcl 4 Nov 2019 10:24:39 -0000 1.7.2.7 @@ -13,7 +13,7 @@ return [${:object} property form ""] } FormGeneratorField instproc render_input {} { - ::xo::Page requireCSS /resources/xowf/myform.css + ::xo::Page requireCSS /resources/xowf/form-generator.css next } @@ -30,7 +30,7 @@ Abstract class for defining common attributes for all Test Item fields. - @param feedback_level "full", or "none" + @param feedback_level "full", "single", or "none" @param auto_correct boolean to let user add auto correction fields } TestItemField set abstract 1 @@ -53,50 +53,50 @@ a question is saved, a HTML form is generated, which is used as a question. - @param feedback_level "full", or "none" + @param feedback_level "full", "single", 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 + # Provide a default setting for the rich-text widgets. # - 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} { + test_item instproc feed_back_definition {} { # # Return the definition of the feed_back widgets depending on the - # value of auto_correct. If we can't determine automatically, - # what's wrong, we can't provide different feedback for right or - # wrong. + # value of :feedback_level. # if {${:feedback_level} eq "none"} { return "" } set widget [test_item set richtextWidget] - if {$auto_correct} { - return [subst { - {feedback_correct {$widget,height=150px,label=#xowf.feedback_correct#}} - {feedback_incorrect {$widget,height=150px,label=#xowf.feedback_incorrect#}} - }] + switch ${:feedback_level} { + "none" { + set definition "" + } + "single" { + set definition [subst { + {feedback_correct {$widget,height=150px,label=#xowf.feedback#}} + }] + } + "full" { + set definition [subst { + {feedback_correct {$widget,height=150px,label=#xowf.feedback_correct#}} + {feedback_incorrect {$widget,height=150px,label=#xowf.feedback_incorrect#}} + }] + } } - return [subst { - {feedback {$widget,label=Korrekturhinweis}} - }] + return $definition } # # "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. + # questionnaires, which might need less input fields. # test_item instproc initialize {} { if {${:__state} ne "after_specs"} { @@ -107,24 +107,39 @@ # Provide some settings for name short-cuts # switch -- ${:question_type} { - mc { # we should support as well: minChoices, maxChoices, shuffle + mc { # we should support as well: minChoices, maxChoices + # + # Old style, kept just for backwards compatibility for the + # time being. one should use "mc2" instead. + # set interaction_class mc_interaction set options nr_choices=[:nr_choices] + set options "" set auto_correct true + set can_shuffle false } - sc { # we should support as well: minChoices, maxChoices, shuffle + sc { # we should support as well: minChoices, maxChoices set interaction_class mc_interaction set options nr_choices=[:nr_choices],multiple=false set auto_correct true + set can_shuffle true } + mc2 { # we should support as well: minChoices, maxChoices + set interaction_class mc_interaction2 + set options "" + set auto_correct true + set can_shuffle true + } ot { set interaction_class text_interaction set auto_correct ${:auto_correct} + set can_shuffle false } te { set interaction_class text_entry_interaction #set options nr_choices=[:nr_choices] set auto_correct ${:auto_correct} + set can_shuffle true } default {error "unknown question type: ${:question_type}"} } @@ -152,11 +167,18 @@ set gradingSpec "" } + if {$can_shuffle} { + set shuffle_options "{None none} {{Per user} peruser} {Always always}" + set shuffleSpec [subst {{shuffle {radio,horizontal=true,options=$shuffle_options,default=none,label=#xowf.Shuffle#}}}] + } else { + set shuffleSpec "" + } :create_components [subst { {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] + $shuffleSpec + {interaction {$interaction_class,$options,feedback_level=${:feedback_level},auto_correct=${:auto_correct},label=}} + [:feed_back_definition] }] set :__initialized 1 } @@ -172,14 +194,14 @@ ########################################################### Class create mc_interaction -superclass TestItemField -parameter { - {shuffle false} {nr_choices 5} {multiple true} } + mc_interaction set auto_correct true mc_interaction instproc set_compound_value {value} { set r [next] - if {![:multiple]} { + if {!${:multiple}} { # For single choice questions, we have a fake-field for denoting # the correct entry. We have to distribute this to the radio # element, which is rendered. @@ -197,12 +219,11 @@ mc_interaction instproc initialize {} { if {${:__state} ne "after_specs"} return - test_item instvar {xinha(javascript) javascript} # # build choices # - if {![:multiple]} { + if {!${:multiple}} { append choices "{correct radio,omit}\n" } # @@ -211,11 +232,11 @@ set widget [test_item set richtextWidget] :create_components [subst { {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}}} + {mc {mc_choice,feedback_level=${:feedback_level},label=#xowf.alternative#,multiple=${:multiple},repeat=1..${:nr_choices}}} }] set :__initialized 1 } - mc_interaction set auto_correct true + mc_interaction instproc convert_to_internal {} { # # Build a form from the components of the exercise on the fly. @@ -231,7 +252,7 @@ set mc [:get_named_sub_component_value mc] ns_log notice "MC <$mc>" - if {![:multiple]} { + if {!${:multiple}} { set correct_field_name [:get_named_sub_component_value correct] } @@ -257,7 +278,7 @@ # # fill values into form # - if {[:multiple]} { + if {${:multiple}} { set correct $value(correct) append form \ "
since this causes a newline in the checkbox label + regexp {^\s*(
)(.*)$} $text . . text + lappend options [list $text [incr count]] + lappend correct [dict get $value $fieldName.correct] + } + set options [::xowiki::formfield::FormField fc_encode $options] + set fc [list answer:checkbox,richtext=1,answer=$correct,shuffle_kind=$shuffle_kind,options=$options] + + append form \ + "
\n" + lappend fc @categories:off @cr_fields:hidden + + ns_log notice "mc_interaction2 $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::mc_field + # + Class create mc_field -superclass TestItemField -parameter { + } + + mc_field instproc initialize {} { + if {${:__state} ne "after_specs"} return + # + # Create component structure. + # + set widget [test_item set richtextWidget] + + if {${:auto_correct}} { + set autoCorrectSpec {{correct_when {correct_when,label=#xowf.correct_when#}}} + } else { + set autoCorrectSpec "" + } + #:msg autoCorrectSpec=$autoCorrectSpec + :create_components [subst { + {text {$widget,height=100px,label=Teilaufgabe,plugins=OacsFs}} + {correct {boolean,horizontal=true,label=Korrekt}} + }] + set :__initialized 1 + } + +} + + namespace eval ::xowiki::formfield { ########################################################### # @@ -561,8 +680,10 @@ set intro_text [:get_named_sub_component_value text] append form \ "\n" append fc \ "@categories:off @cr_fields:hidden\n" \ @@ -679,6 +800,80 @@ } } +namespace eval ::xowf::test_item { + + nx::Object create renaming_form_loader { + # + # Form loader that renames "generic" form-field-names as provided + # by the test-item form-field classes (@answer@) into names based + # on the form name, such that multiple of these form names can be + # processed together without name clashes. + # + + :object method map_form_constraints {form_constraints oldName newName} { + # + # Rename form constraints starting with $oldName into $newName. + # Handle as well "answer=$oldName" form constraint properties. + # + return [lmap f $form_constraints { + #:msg check?'$f' + if {[string match "${oldName}*" $f]} { + regsub $oldName $f $newName f + if {[string match "*answer=$oldName*" $f]} { + regsub answer=$oldName $f answer=$newName f + #:log "MAP VALUE=answer=$oldName => answer=$newName " + } + } + set f + }] + } + + :public object method form_name_based_attribute_stem {formName} { + # + # Produce from the provided 'formName' an attribute stem for the + # input fields of this form. + # + set strippedName [lindex [split $formName :] end] + regsub -all {[-]} $strippedName _ stem + return ${stem}_ + } + + :public object method get_form_object {{-set_title:boolean true} ctx form_name} { + #:msg "renaming_form_loader for form_name <$form_name>" + set form_id [$ctx default_load_form_id $form_name] + set obj [$ctx object] + set form_obj [::xo::db::CrClass get_instance_from_db -item_id $form_id] + + set form [$form_obj get_property -name form] + set fc [$form_obj get_property -name form_constraints] + + # + # Map "answer" to a generic name in the form "@answer@" and in the + # form constraints. + # + set newName [:form_name_based_attribute_stem [$form_obj name]] + + regsub -all {@answer} $form @$newName form + set fc [:map_form_constraints $fc "answer" $newName] + set disabled_fc [lmap f $fc { + if {[string match "$newName*" $f]} { append f ,disabled=true } + set f + }] + + lappend fc @cr_fields:hidden + lappend disabled_fc @cr_fields:hidden + #:msg fc=$fc + + $form_obj set_property form $form + $form_obj set_property -new 1 form_constraints $fc + $form_obj set_property -new 1 disabled_form_constraints $disabled_fc + + return $form_obj + } + + } +} + # # Local variables: # mode: tcl