%3 ::xowiki::formfield::FormField ::xowiki::formfield::FormField → fc_decode → fc_encode → get_from_name → get_single_spec → interprete_condition CSSclass_list_add add_statistics answer_check=AND answer_check=answer_words answer_check=btwn answer_check=contains answer_check=contains-not answer_check=eq answer_check=ge answer_check=gt answer_check=in answer_check=le answer_check=lt answer_check=match answer_is_correct asWidgetSpec behavior booleanAttributes config_from_spec convert_to_external convert_to_internal describe dict_to_fc dict_to_spec dict_value escape_message_keys field_value handle_transmit_always has_instance_variable init initialize interprete_single_spec is_disabled is_repeat_template_p leaf_components localize make_correct pretty_image pretty_value process_correct_when_modifier remove_omit render render_answer_statistics render_collapsed render_disabled_as_div render_error_msg render_form_widget render_help_text render_input render_item render_localizer render_modal render_result_statistics render_word_statistics repeat repeat_add_label resetBooleanAttributes reset_on_validation_error reset_parameter same_value set_disabled set_feedback set_is_repeat_template stats_record_count validate validation_check value_if_nothing_is_returned_from_form word_statistics ::xo::tdom::Object ::xo::tdom::Object render ::xowiki::formfield::FormField->::xo::tdom::Object ::xowiki::MenuComponent ::xowiki::MenuComponent html_id js_name ::xowiki::MenuComponent->::xo::tdom::Object ::xo::tdom::AttributeManager ::xo::tdom::AttributeManager ::xo::tdom::Object->::xo::tdom::AttributeManager ::xo::OrderedComposite ::xo::OrderedComposite ::xo::tdom::Object->::xo::OrderedComposite ::xowiki::formfield::number ::xowiki::formfield::number initialize render_input ::xowiki::formfield::number->::xowiki::formfield::FormField ::xowiki::formfield::boolean_image ::xowiki::formfield::boolean_image initialize render_input ::xowiki::formfield::boolean_image->::xowiki::formfield::FormField ::xowiki::formfield::omit ::xowiki::formfield::omit render_help_text render_item ::xowiki::formfield::omit->::xowiki::formfield::FormField ::xowiki::formfield::CompoundField ::xowiki::formfield::CompoundField add_component add_statistics check=compound convert_to_external convert_to_internal create_components exists_named_sub_component generate_fieldnames get_component get_compound_value get_named_sub_component get_named_sub_component_value has_instance_variable leaf_components make_correct named_sub_components object pretty_value render_input reset_on_validation_error same_value set_compound_value set_disabled set_is_repeat_template specs_unmodified validate value ::xowiki::formfield::CompoundField->::xowiki::formfield::FormField ::xowiki::formfield::inform ::xowiki::formfield::inform initialize render_help_text render_input ::xowiki::formfield::inform->::xowiki::formfield::FormField ::xowiki::formfield::hidden ::xowiki::formfield::hidden check=signature initialize render_help_text render_item ::xowiki::formfield::hidden->::xowiki::formfield::FormField ::xowiki::formfield::label ::xowiki::formfield::label pretty_value render_input render_item ::xowiki::formfield::label->::xowiki::formfield::FormField ::xowiki::formfield::text ::xowiki::formfield::text add_statistics initialize is_clock_string_p render_input ::xowiki::formfield::text->::xowiki::formfield::FormField ::xowiki::formfield::range ::xowiki::formfield::range initialize render_input ::xowiki::formfield::range->::xowiki::formfield::FormField ::xowiki::formfield::ShuffleField ::xowiki::formfield::ShuffleField check=options initialize randomized_indices shuffle_options valid_subselection ::xowiki::formfield::ShuffleField->::xowiki::formfield::FormField ::xowiki::formfield::textarea ::xowiki::formfield::textarea add_statistics clear_editor_mixins initialize render_as_div render_input set_feedback ::xowiki::formfield::textarea->::xowiki::formfield::FormField ::xowiki::formfield::submit_button ::xowiki::formfield::submit_button initialize render_input ::xowiki::formfield::submit_button->::xowiki::formfield::FormField ::xowf::test_item::td_pretty_value ::xowf::test_item::td_pretty_value pretty_value ::xowf::test_item::td_pretty_value->::xowiki::formfield::FormField ::xowf::Property ::xowf::Property get_default_from init wf_context ::xowf::Property->::xowiki::formfield::FormField ::xowiki::formfield::file ::xowiki::formfield::file check=virus content-type convert_to_internal entry_info get_from_value get_old_value initialize label_or_value no_value_provided pretty_value render_input reset_on_validation_error store_file tmpfile value ::xowiki::formfield::file->::xowiki::formfield::FormField ::xowiki::formfield::grade_boundary ::xowiki::formfield::grade_boundary render_input ::xowiki::formfield::grade_boundary->::xowiki::formfield::number ::xowiki::formfield::repeatContainer ::xowiki::formfield::repeatContainer check_nr_components component_item_spec convert_to_internal count_values initialize item_spec pretty_value render_input require_component set_compound_value trim_values validate ::xowiki::formfield::repeatContainer->::xowiki::formfield::CompoundField ::xowiki::formfield::regression_test_mycompound ::xowiki::formfield::regression_test_mycompound initialize ::xowiki::formfield::regression_test_mycompound->::xowiki::formfield::CompoundField ::xowiki::formfield::text_fields ::xowiki::formfield::text_fields answer_is_correct get_text_entry initialize pretty_value render_help_text render_input set_feedback td_pretty_value ::xowiki::formfield::text_fields->::xowiki::formfield::CompoundField ::xowiki::formfield::text_fields->::xowiki::formfield::ShuffleField ::xowiki::formfield::regression_test_compound_with_repeat2 ::xowiki::formfield::regression_test_compound_with_repeat2 initialize ::xowiki::formfield::regression_test_compound_with_repeat2->::xowiki::formfield::CompoundField ::xowiki::formfield::mc_exercise ::xowiki::formfield::mc_exercise convert_to_internal initialize pretty_value render_input ::xowiki::formfield::mc_exercise->::xowiki::formfield::CompoundField ::xowiki::formfield::date ::xowiki::formfield::date convert_to_external get_compound_value initialize pretty_value render_input same_value set_compound_value ::xowiki::formfield::date->::xowiki::formfield::CompoundField ::xowiki::formfield::CalendarField ::xowiki::formfield::CalendarField update_calendar ::xowiki::formfield::CalendarField->::xowiki::formfield::CompoundField ::xowiki::formfield::repeattest ::xowiki::formfield::repeattest initialize ::xowiki::formfield::repeattest->::xowiki::formfield::CompoundField ::xowiki::formfield::mc_alternative ::xowiki::formfield::mc_alternative initialize ::xowiki::formfield::mc_alternative->::xowiki::formfield::CompoundField ::xowiki::formfield::FormGeneratorField ::xowiki::formfield::FormGeneratorField pretty_value render_input ::xowiki::formfield::FormGeneratorField->::xowiki::formfield::CompoundField ::xowiki::formfield::regression_test_compound_with_repeat ::xowiki::formfield::regression_test_compound_with_repeat initialize ::xowiki::formfield::regression_test_compound_with_repeat->::xowiki::formfield::CompoundField ::xowiki::formfield::regression_test_compound_numeric ::xowiki::formfield::regression_test_compound_numeric initialize ::xowiki::formfield::regression_test_compound_numeric->::xowiki::formfield::CompoundField ::xowiki::formfield::comp_correct_when ::xowiki::formfield::comp_correct_when initialize ::xowiki::formfield::comp_correct_when->::xowiki::formfield::CompoundField ::xowiki::formfield::child_pages ::xowiki::formfield::child_pages initialize pretty_value ::xowiki::formfield::child_pages->::xowiki::formfield::label ::xowiki::formfield::current_state ::xowiki::formfield::current_state pretty_value render_input ::xowiki::formfield::current_state->::xowiki::formfield::label ::xowiki::formfield::week ::xowiki::formfield::week initialize ::xowiki::formfield::week->::xowiki::formfield::text ::xowiki::formfield::color ::xowiki::formfield::color initialize ::xowiki::formfield::color->::xowiki::formfield::text ::xowiki::formfield::redirect ::xowiki::formfield::redirect pretty_value ::xowiki::formfield::redirect->::xowiki::formfield::text ::xowiki::formfield::password ::xowiki::formfield::password initialize ::xowiki::formfield::password->::xowiki::formfield::text ::xowiki::formfield::search ::xowiki::formfield::search initialize ::xowiki::formfield::search->::xowiki::formfield::text ::xowiki::formfield::time ::xowiki::formfield::time initialize ::xowiki::formfield::time->::xowiki::formfield::text ::xowiki::formfield::test_item_name ::xowiki::formfield::test_item_name check=name ::xowiki::formfield::test_item_name->::xowiki::formfield::text ::xowiki::formfield::email ::xowiki::formfield::email initialize ::xowiki::formfield::email->::xowiki::formfield::text ::xowiki::formfield::numeric ::xowiki::formfield::numeric answer_check=eq check=numeric convert_to_external convert_to_internal convert_to_internal_value initialize pretty_value render_input ::xowiki::formfield::numeric->::xowiki::formfield::text ::xowiki::formfield::datetime ::xowiki::formfield::datetime initialize ::xowiki::formfield::datetime->::xowiki::formfield::text ::xowiki::formfield::h5date ::xowiki::formfield::h5date check=valid_format initialize ::xowiki::formfield::h5date->::xowiki::formfield::text ::xowiki::formfield::h5time ::xowiki::formfield::h5time check=valid_format initialize ::xowiki::formfield::h5time->::xowiki::formfield::text ::xowiki::formfield::youtube_url ::xowiki::formfield::youtube_url initialize pretty_value ::xowiki::formfield::youtube_url->::xowiki::formfield::text ::xowiki::formfield::image_url ::xowiki::formfield::image_url check=image_check entry_name initialize pretty_value ::xowiki::formfield::image_url->::xowiki::formfield::text ::xowiki::formfield::url ::xowiki::formfield::url check=safe_url initialize pretty_value ::xowiki::formfield::url->::xowiki::formfield::text ::xowiki::formfield::tel ::xowiki::formfield::tel initialize ::xowiki::formfield::tel->::xowiki::formfield::text ::xowiki::formfield::datetime-local ::xowiki::formfield::datetime-local check=valid_format initialize ::xowiki::formfield::datetime-local->::xowiki::formfield::text ::xowiki::formfield::include ::xowiki::formfield::include pretty_value ::xowiki::formfield::include->::xowiki::formfield::text ::xowiki::formfield::localized_text ::xowiki::formfield::localized_text build_message_key_name convert_to_internal escape_message_keys pretty_value render_input ::xowiki::formfield::localized_text->::xowiki::formfield::text ::xowiki::formfield::correct_when ::xowiki::formfield::correct_when check=valid_predicate initialize ::xowiki::formfield::correct_when->::xowiki::formfield::text ::xowiki::formfield::enumeration ::xowiki::formfield::enumeration add_statistics answer_is_correct config_from_category_tree get_labels ggw initialize make_correct pretty_value render_input render_label_classes render_label_text render_result_statistics scores stats_record_detail value_if_nothing_is_returned_from_form ::xowiki::formfield::enumeration->::xowiki::formfield::ShuffleField ::xowiki::formfield::workflow_definition ::xowiki::formfield::workflow_definition as_graph check=workflow pretty_value ::xowiki::formfield::workflow_definition->::xowiki::formfield::textarea ::xowiki::formfield::menuentries ::xowiki::formfield::menuentries pretty_value ::xowiki::formfield::menuentries->::xowiki::formfield::textarea ::xowiki::formfield::code_listing ::xowiki::formfield::code_listing pretty_value ::xowiki::formfield::code_listing->::xowiki::formfield::textarea ::xowiki::formfield::richtext ::xowiki::formfield::richtext check=safe_html editor initialize preset_conf pretty_value ::xowiki::formfield::richtext->::xowiki::formfield::textarea ::xowiki::formfield::form_constraints ::xowiki::formfield::form_constraints ::xowiki::formfield::form_constraints->::xowiki::formfield::textarea ::xowiki::formfield::import_archive ::xowiki::formfield::import_archive initialize pretty_value ::xowiki::formfield::import_archive->::xowiki::formfield::file ::xowiki::formfield::image ::xowiki::formfield::image pretty_value ::xowiki::formfield::image->::xowiki::formfield::file

Class ::xowiki::formfield::FormField

::xowiki::formfield::FormField[i] create ... \
           [ -CSSclass (default "form-control") ] \
           [ -form_button_CSSclass (default "btn btn-default") ] \
           [ -form_button_wrapper_CSSclass (default "") ] \
           [ -form_help_text_CSSclass (default "help-block") ] \
           [ -form_item_wrapper_CSSclass (default "form-group") ] \
           [ -form_label_CSSclass (default "") ] \
           [ -form_widget_CSSclass (default "") ]

Defined in

Class Relations

  • class: ::xotcl::Class[i]
  • superclass: ::xo::tdom::Object[i]
  • subclass: ::xowiki::formfield::number[i], ::xowiki::formfield::boolean_image[i], ::xowiki::formfield::omit[i], ::xowiki::formfield::CompoundField[i], ::xowiki::formfield::inform[i], ::xowiki::formfield::hidden[i], ::xowiki::formfield::label[i], ::xowiki::formfield::text[i], ::xowiki::formfield::range[i], ::xowiki::formfield::ShuffleField[i], ::xowiki::formfield::textarea[i], ::xowiki::formfield::submit_button[i], ::xowf::test_item::td_pretty_value[i], ::xowf::Property[i], ::xowiki::formfield::file[i]
::xotcl::Class create ::xowiki::formfield::FormField \
     -superclass ::xo::tdom::Object

Methods (to be applied on the object)

  • fc_decode (scripted)

     xowiki::formfield::FormField[i] fc_decode

    Testcases:
    dict_to_xx, xowiki
    return [string map [list __COMMA__ ,] $string]
  • fc_encode (scripted)

     xowiki::formfield::FormField[i] fc_encode

    Testcases:
    dict_to_xx, xowiki
    return [string map [list , __COMMA__] $string]
  • get_from_name (scripted)

    #
    # Get a form field via name. The provided names are unique for a
    # form. If multiple forms should be rendered simultaneously, we
    # have to extend the addressing mechanism.
    #
    # todo: we could speed this up by an index if needed
    foreach f [::xowiki::formfield::FormField info instances -closure] {
      if {[$f name] eq $name} {
        if {![$f exists object]} {
          #ad_log warning "strange, $f [$f name] was created without object but fits name"
          return $f
        } elseif {$object eq [$f object]} {
          return $f
        }
      }
    }
    #:msg not-found-$object-$name
    return ""
  • get_single_spec (scripted)

    if {[regexp ${:cond_regexp} $string _ condition true_spec false_spec]} {
      if {[:interprete_condition -package_id $package_id -object $object $condition]} {
        return [:get_single_spec -package_id $package_id -object $object $true_spec]
      } else {
        return [:get_single_spec -package_id $package_id -object $object $false_spec]
      }
    }
    return $string
  • interprete_condition (scripted)

    if {[::xo::cc info methods role=$cond] ne ""} {
      if {$cond eq "creator"} {
        set success [::xo::cc role=$cond  -object $object  -user_id [::xo::cc user_id]  -package_id $package_id]
      } else {
        set success [::xo::cc role=$cond  -user_id [::xo::cc user_id]  -package_id $package_id]
      }
    } else {
      set success [$object evaluate_form_field_condition $cond]
    }
    return $success

Methods (to be applied on instances)

  • CSSclass (setter)

  • CSSclass_list_add (scripted)

    #
    # Convenience function to add a CSS class to the existing values
    # in "attrName". The function is named somewhat similar to the
    # JavaScript function "classList.add"
    #
    if {$value ne ""} {
      if {[info exists :$attrName]} {
        append :$attrName " " $value
      } else {
        set :$attrName $value
      }
    }
  • add_statistics (scripted)

    #
    # The FormField based incremental statistic counter is deprecated,
    # since for randomization, it is necessary to reinitialized
    # form-fields of an exam multiple times (different students have
    # different alternatives, etc.).  Therefore, statistics have to be
    # collected in general on the level of the workflow object (via
    # $reporting_obj stats_record_*).
    #
    dict incr :result_statistics count
    #ns_log notice "[self] enumeration add_statistics count -> [dict get ${:result_statistics} count]"
    if {[info exists :evaluated_answer_result] && ${:evaluated_answer_result} eq "correct"} {
      #ns_log notice "[self] enumeration add_statistics count -> [dict get ${:result_statistics} count] correct"
      dict incr :result_statistics correct
      #ns_log notice "??? add_statistics ${:name}: ${:result_statistics}"
    }
    dict incr :answer_statistics ${:value}
  • answer (setter)

  • answer_check=AND (scripted)

    set results ""
    #
    # The AND clause iterates over the list elements and reduces the
    # composite AND into multiple simple clauses and overwrites (!
    # danger) these in the instance variable "correct_when", but
    # resets these at the end.
    #
    set composite_correct_when ${:correct_when}
    ns_log notice "${:name} ... answercheck ${:correct_when}"
    foreach clause [lrange ${:correct_when} 1 end] {
      set :correct_when $clause
      ns_log notice "${:name} ... AND '$clause' for '${:value}' -> [:answer_is_correct]"
      lappend results [:answer_is_correct]
    }
    set :correct_when $composite_correct_when
    ns_log notice  "${:name} $composite_correct_when => $results"
    if {0 in $results} {
      #
      # When one element is undecided, all is undecided.
      #
      return 0
    }
    #
    # If one element is wrong, all is wrong
    #
    return [expr {-1 ni $results}]
  • answer_check=answer_words (scripted)

    #
    # Correct, when the answer is equal to the provided (sequence of)
    # words, but white-space is ignored. When the first word is
    # "*lower*" then the provided answer of the student is converted
    # to lowercase before the comparison is performed; as a
    # consequence the comparison is not case sensitive. Note that the
    # answer_words have to be provided in lowercase as well.
    #
    set d [:process_correct_when_modifier]
    dict with d {
      return [expr {$value eq $words}]
    }
  • answer_check=btwn (scripted)

    set d [:process_correct_when_modifier]
    dict with d {
      return [expr {$value >= [lindex $words 0] && $value <= [lindex $words 1]}]
    }
  • answer_check=contains (scripted)

    #
    # Correct, when answer contains any of the provided words.
    #
    set d [:process_correct_when_modifier]
    foreach word [dict get $d words] {
      if {[string match *$word* [dict get $d value]]} {
        return 1
      }
    }
    return 0
  • answer_check=contains-not (scripted)

    #
    # Correct, when answer does not contain any of the provided
    # words. Just a negation of "contains".
    #
    return [expr {[:answer_check=contains] ? 0 : 1}]
  • answer_check=eq (scripted)

    set d [:process_correct_when_modifier]
    dict with d {
      return [expr {$value eq [lindex $words 0]}]
    }
  • answer_check=ge (scripted)

    set d [:process_correct_when_modifier]
    dict with d {
      return [expr {$value >= [lindex $words 0]}]
    }
  • answer_check=gt (scripted)

    set d [:process_correct_when_modifier]
    dict with d {
      return [expr {$value > [lindex $words 0]}]
    }
  • answer_check=in (scripted)

    #
    # Correct, when answer is in the given set.
    #
    set d [:process_correct_when_modifier]
    dict with d {
      return [expr {$value in $words}]
    }
  • answer_check=le (scripted)

    set d [:process_correct_when_modifier]
    dict with d {
      return [expr {$value ne "" && $value <= [lindex $words 0]}]
    }
  • answer_check=lt (scripted)

    set d [:process_correct_when_modifier]
    dict with d {
      return [expr {$value ne "" && $value < [lindex $words 0]}]
    }
  • answer_check=match (scripted)

    set d [:process_correct_when_modifier]
    dict with d {
      return [string match [lindex $words 0] $value]
    }
  • answer_is_correct (scripted)

    #
    # Return correctness of the answer based on the instance variables
    # ":correct_when" and ":answer". Possible results are
    #
    #  -  1: correct
    #  - -1: incorrect
    #  -  0: can't say (or report back, that no evaluation should be
    #        provided for this field)
    #
    #  This method is free of side-effects (no instance variables are updated).
    #
    #:log "CORRECT? ${:name} ([:info class]): value=${:value}, answer=[expr {[info exists :answer]?${:answer}:{NONE}}]"
    if {[info exists :correct_when] && ${:correct_when} ne ""} {
      set op [lindex ${:correct_when} 0]
      #:log "CORRECT? ${:name} with op=$op '${:correct_when}'"
      if {[:procsearch answer_check=$op] ne ""} {
        set r [:answer_check=$op]
        if {$r == 0} {
          return -1
        } {
          return 1
        }
      } elseif$op eq ""} {
        return 0
      } else {
        error "invalid operator '$op'"
      }
    } elseif {![info exists :answer]} {
      return 0
    } elseif {${:value} ne ${:answer}} {
      #:msg "v='${:value}' NE a='[:answer]'"
      #:log "... answer_is_correct value comparison '${:value}' NE a='${:answer}'"
      return -1
    } else {
      #:log "... answer_is_correct value comparison OK"
      return 1
    }
  • asWidgetSpec (scripted)

    set spec ${:widget_type}
    if {[info exists :spell]} {
      append spec ",[expr {${:spell} ? {} : {no}}]spell"
    }
    
    if {!${:required}} {
      append spec ",optional"
    }
    if {[info exists :editor]} {
      append spec " {options {editor ${:editor}}} "
    }
    append spec " {label " [list ${:label}"} "
    
    if {[string match "*bootstrap*" [subsite::get_theme]]} {
      # TODO: we need a better solution for this
      if {[::template::CSS toolkit] eq "bootstrap5"
          && ([:hasclass ::xowiki::formfield::radio] || [:hasclass ::xowiki::formfield::checkbox])
        } {
        array set :html {class "form-check-input"}
      } else {
        array set :html {class "form-control"}
      }
    }
    
    if {[info exists :html]} {
      append spec " {html {"
      foreach {key value} [array get :html] {
        append spec $key " " [list $value" "
      }
      append spec "}} "
    }
    
    if {[info exists :options]} {
      append spec " {options " [list ${:options}"} "
    }
    if {[info exists :format]} {
      append spec " {format " [list ${:format}"} "
    }
    
    if {${:help_text} ne ""} {
      if {[string match "#*#" ${:help_text}]} {
        set internationalized [:localize ${:help_text}]
        append spec " {help_text {$internationalized}}"
      } else {
        append spec " {help_text {${:help_text}}}"
      }
    }
    #ns_log notice "${:name} === asWidgetSpec return $spec"
    return $spec
  • autocomplete (setter)

  • autofocus (setter)

  • behavior (scripted)

    #
    # Specify the behavior of a form field via
    # per object mixins
    #
    set pkgctx [[${:object} package_id] context]
    if {[$pkgctx exists embedded_context]} {
      set ctx [$pkgctx set embedded_context]
      set classname ${ctx}::$mixin
      #:msg ctx=$ctx-viewer=$mixin,found=[:isclass $classname]
      # TODO: search different places for the mixin. Special namespace?
      if {[:isclass $classname]} {
        if {[info exists :per_object_behavior]} {
          :mixin delete ${:per_object_behavior}
        }
        :mixin add $classname
        set :per_object_behavior $classname
      } else {
        :msg "Could not find mixin '$mixin'"
      }
    }
  • booleanAttributes (scripted)

    #
    # Special handling of HTML boolean attributes, since they require a
    # different coding; it would be nice, if tDOM would care for this.
    #
    set pairs ""
    foreach att $args {
      if {[info exists :$att] && [set :$att]} {
        set :__#$att $att
        lappend pairs [list __#$att $att]
      }
    }
    return $pairs
  • config_from_spec (scripted)

    #:log "config_from_spec ${:name} spec <$spec> [:info class] [[:info class] exists abstract]"
    if {[[:info class] exists abstract]} {
      # had earlier here: [:info class] eq [self class]
      # Check, whether the actual class is a concrete class (mapped to
      # concrete field type) or an abstract class.  Since
      # config_from_spec can be called multiple times, we want to do
      # the reclassing only once.
      if {[:isclass ::xowiki::formfield::${:type}]} {
        :class ::xowiki::formfield::${:type}
      } else {
        :class ::xowiki::formfield::text
      }
      # set missing instance vars with defaults
      :set_instance_vars_defaults
    }
    regsub -all -- {,\s+} $spec , spec
    foreach s [split $spec ,] {
      :interprete_single_spec [FormField fc_decode $s]
    }
    
    #:msg "${:name}: after specs"
    set :__state after_specs
    #:log "INITIALIZE ${:name} due to config_from_spec"
    :initialize
    
    #
    # It is possible, that a default value of a form field is changed through a spec.
    # Since only the configuration might set values, checking value for "" seems safe here.
    #
    if {[:value] eq "" && [info exists :default] && ${:default} ne ""} {
      #:msg "+++ reset value to [:default]"
      :value ${:default}
    }
    
    if {[lang::util::translator_mode_p]} {
      :mixin add "::xo::TRN-Mode"
    }
  • convert_to_external (scripted)

    # To be overloaded.
    return $value
  • convert_to_internal (scripted)

    # To be overloaded.
  • correct_when (setter)

  • default (setter)

  • describe (scripted)

    set d ""
    #
    # The dict keys of the result should correspond as far as possible
    # to message keys to ease multi-language communication.
    #
    set qa [${:object} property question]
    #ns_log notice "FormField describe gets question attributes <$qa> from ${:object}"
    
    foreach {key name} {
      question.minutes Minutes
      question.points Points
      question.grading grading
      question.show_max show_max
    } {
      if {[dict exists $qa $key]} {
        dict set d $name [dict get $qa $key]
      }
    }
    switch [:info class] {
      ::xowiki::formfield::radio -
      ::xowiki::formfield::checkbox {
        # mc and sc interaction
        set type [expr {[:info class] eq "::xowiki::formfield::checkbox" ? "MC" : "SC"}]
        #
        # The factual (displayed) answer is in ${:answer}, but we want
        # to see the list of possibilities, so use the data from the
        # original spec.
        #
        foreach s [split ${:spec} ,] {
          if {[regexp {^answer=(.*)$} $s . answer]} {
            break
          }
        }
        dict set d choice_options [llength ${answer}]
        dict set d nrcorrect [llength [lsearch -exact -all ${answer} t]]
        dict set d shuffle ${:shuffle_kind}
        #dict set d all [:serialize]
      }
      ::xowiki::formfield::text_fields {
        set type ShortText
        # short text interaction
        #
        # The factual (displayed) answer is in ${:answer}, but we want
        # to see the list of possibilities, so use the data from the
        # original spec (here in $options)
        #
        foreach s [split ${:spec} ,] {
          #ns_log warning "s=$s"
          if {[regexp {^options=(.*)$} $s . options]} {
            break
          }
        }
        dict set d all ${:spec}
        dict set d sub_questions [llength ${options}]
        dict set d shuffle ${:shuffle_kind}
      }
      ::xowiki::formfield::textarea {
        set type Text
      }
      ::xowiki::formfield::pool_question_placeholder {
        set type PoolQuestion
        set item_dict [:QM get_pool_questions  -field_name $field_name ${:object} ""]
    
        set counts ""
        foreach {key value} $item_dict {
          dict incr counts [dict get $item_dict $key item_type]
        }
    
        dict set d available_pool_items [dict size $item_dict]
        dict set d available_pool_item_stats $counts
      }
      ::xowiki::formfield::reorder_box {
        set type Reorder
      }
      ::xowiki::formfield::file {
        set type Upload
      }
      ::xowiki::formfield::form_page {
        set type Composite
      }
    
      default {
        set type [:info class]
        ns_log warning "describe: class [:info class] not handled"
      }
    }
    dict set d type $type
    #ns_log notice "describe [:info class] [${:object} name] -> $d"
    dict set d question_title [${:object} title]
    
    return $d
  • dict_to_fc (forward)

     <instance of xowiki::formfield::FormField[i]> dict_to_fc

    Testcases:
    create_test_items, xowf
  • dict_to_spec (forward)

  • dict_value (forward)

  • disabled (setter)

  • disabled_as_div (setter)

  • display_field (setter)

  • error_msg (setter)

  • escape_message_keys (scripted)

    #
    # Can be overloaded, when e.g. no escaping of message keys or
    # other representations of message keys are desired.
    #
    return [xo::escape_message_keys $value]
  • feedback_answer_correct (setter)

  • feedback_answer_incorrect (setter)

  • field_value (scripted)

    if {[info exists :show_raw_value]} {
      return $v
    } else {
      return [:pretty_value $v]
    }
  • form_button_CSSclass (setter)

  • form_button_wrapper_CSSclass (setter)

  • form_help_text_CSSclass (setter)

  • form_item_wrapper_CSSclass (setter)

  • form_label_CSSclass (setter)

  • form_widget_CSSclass (setter)

  • formnovalidate (setter)

  • grading (setter)

  • handle_transmit_always (scripted)

    #
    # Disabled fields are not returned by the browsers. For some
    # fields, we require to be sent. Therefore, we include in these
    # cases the value in an additional hidden field. Maybe we should
    # change in the future the "name" of the disabled entry to keep
    # some hypothetical html-checker quiet.
    #
    if {[:is_disabled] && [info exists :transmit_field_always]} {
      ::html::div {
        ::html::input [list type hidden name ${:name} value $value] {}
      }
    }
  • has_instance_variable (scripted)

    if {[info exists :$var] && [set :$var] eq $value} {return 1}
    return 0
  • help_text (setter)

  • hide_value (setter)

  • id (setter)

  • in_position (setter)

  • init (scripted)

    if {![info exists :label]} {
      set :label [string totitle ${:name}]
    }
    if {![info exists :id]} {
      set :id ${:name}
    }
    set :html(id) ${:id}
    #if {[info exists :default]} {set :value [:default]}
    :config_from_spec ${:spec}
  • initialize (scripted)

    next
  • inline (setter)

  • interprete_single_spec (scripted)

    if {$s eq ""} return
    
    #ns_log notice "${:name} interprete_single_spec '$s'"
    set package_id [${:object} package_id]
    set s [::xowiki::formfield::FormField get_single_spec -object ${:object} -package_id $package_id $s]
    
    switch -glob -- $s {
      optional    {set :required false}
      required    {set :required true; :remove_omit}
      omit        {:mixin add ::xowiki::formfield::omit}
      noomit      {:remove_omit}
      disabled    {:set_disabled true}
      readonly    {:readonly true}
      enabled     {:set_disabled false}
      label=*     {:label     [lindex [split $s =] 1]}
      help_text=* {:help_text [lindex [split $s =] 1]}
      *=*         {
        set p [string first = $s]
        set attribute [string range $s 0 $p-1]
        set value [string range $s $p+1 end]
        set definition_class [lindex [:procsearch $attribute] 0]
        set method [:info methods $attribute]
        if {[string match "::xotcl::*" $definition_class] || $method eq ""} {
          error [_ xowiki.error-form_constraint-unknown_attribute [list class [:info class] name ${:name} entry $attribute]]
        }
        ad_try {
          #
          # We want to allow a programmer to use e.g. options=[xowiki::locales]
          #
          # Note: do not allow users to use [] via forms, since they might
          # execute arbitrary commands. The validator for the form fields
          # makes sure that the input specs are free from square brackets.
          #
          if {[string match {\[*\]} $value]} {
            set value [subst $value]
          }
          :$attribute $value
        } on error {errMsg} {
          error "Error during setting attribute '$attribute' to value '$value': $errMsg"
        }
      }
      default {
        # Check, if the spec value $s is a class.
        set old_class [:info class]
        # Don't allow one to use namespaced values, since we would run
        # into a recursive loop for richtext::wym (could be altered there as well).
        if {[:isclass ::xowiki::formfield::$s] && ![string match "*:*" $s]} {
          :class ::xowiki::formfield::$s
          :remove_omit
          if {$old_class ne [:info class]} {
            #:msg "${:name}: reset class from $old_class to [:info class]"
            :reset_parameter
            set :__state reset
            #:log "INITIALIZE ${:name} due to reclassing old $old_class to new [:info class]"
            :initialize
          }
        } else {
          if {$s ne ""} {
            error [_ xowiki.error-form_constraint-unknown_spec_entry  [list name ${:name} entry $s x "Unknown spec entry for entry '$s'"]]
          }
        }
      }
    }
  • is_disabled (scripted)

    return [expr {[info exists :disabled] && [string is true -strict ${:disabled}]}]
  • is_repeat_template_p (scripted)

    return [expr {[info exists :is_repeat_template] && ${:is_repeat_template} == "true"}]
  • label (setter)

  • label_noquote (setter)

  • leaf_components (scripted)

    #
    # We want to be able to be able to call leaf_components on
    # arbitrary form-fields, so return for non-composite fields just a
    # list with a single element.
    #
    return [list [self]]
  • locale (setter)

  • localize (scripted)

    # We localize in pretty_value the message keys in the
    # language of the item (not the connection item).
    if {[regexp "^#(.*)#$" $v _ key]} {
      return [lang::message::lookup ${:locale} $key]
    }
    return $v
  • make_correct (scripted)

    #
    # Set the form_field to a correct value, currently based on
    # :correct_when.  We could use here :answer when available.
    # Modified instance variables
    #
    # - :help_text is cleared to avoid stray per-user-feedback,
    #    We could as well provide teacher-level feedback here.
    # - :form_widget_CSSclass is altered to "correct" or "unknown".
    #
    #ns_log notice "FormField make_correct ${:name}: [info exists :answer] [info exists :correct_when]"
    
    set :form_widget_CSSclass unknown
    if {[info exists :correct_when]} {
      #
      # Try to get a correct value from the correct_when spec
      #
      set predicate [lindex ${:correct_when} 0]
      set args [lrange ${:correct_when} 1 end]
      switch $predicate {
        "match"    {set correct $args}
        "eq"       {set correct $args}
        "gt"       {set correct "> $args"}
        "ge"       {set correct ">= $args"}
        "lt"       {set correct "< $args"}
        "le"       {set correct "<= $args"}
        "contains" {set correct "... [join $args { ... }] ..."}
        "answer_words" {set correct "... [join $args { ... }] ..."}
        "in"       {set correct "... [join args { OR }] ..."}
        "btwn"     {set correct "[lindex $args 0] <= X <= [lindex $args 1]"}
    
      }
      if {[info exists correct]} {
        :value $correct
        set :form_widget_CSSclass correct
        #ns_log notice "FormField make_correct ${:name}: value '${:value}'"
      } else {
        ns_log notice "FormField make_correct ${:name}: not handled: correct_when '${:correct_when}' "
      }
    } else {
      ns_log notice "FormField make_correct ${:name}: not handled: answer? [info exists :answer]"
    }
    set :help_text "" ;# we could provide a teacher-level feedback here.
  • mode (setter)

  • multiple (setter)

  • name (setter)

  • object (setter)

  • pattern (setter)

  • placeholder (setter)

  • pretty_image (scripted)

    if {$entry_name eq "" || ${:value} eq ""} return
    
    set item_ref [${:object} item_ref -default_lang [${:object} lang] -parent_id $parent_id $entry_name]
    
    set label ${:label} ;# the label is used for alt and title
    if {$label eq [dict get $item_ref stripped_name]} {
      #
      # The label is apparently the default. For Photo.form instances,
      # this is always "image". In such cases, use the title of the
      # parent object as label.
      #
      set label [${:object} title]
    }
    set l [::xowiki::Link create new  -page ${:object} -type "image"  -lang [dict get $item_ref prefix]  -stripped_name [dict get $item_ref stripped_name]  -label $label  -parent_id [dict get $item_ref parent_id]  -item_id [dict get $item_ref item_id]  -destroy_on_cleanup]
    
    if {[:istype file]} {
      if {$revision_id ne ""} {
        $l revision_id $revision_id
      }
    }
    
    foreach option {
      href cssclass
      float width height
      padding padding-right padding-left padding-top padding-bottom
      margin margin-left margin-right margin-top margin-bottom
      border border-width position top bottom left right
      geometry
    } {
      if {[info exists :$option]} {$l set $option [set :$option]}
    }
    set html [$l render]
    return $html
  • pretty_value (scripted)

    #:log "mapping $v"
    return [string map [list & "&amp;" < "&lt;" > "&gt;" \" "&quot;" ' "&#39;" @ "&#64;"$v]
  • process_correct_when_modifier (scripted)

    #
    # Return a dict containing "words", "value" and "modifier",
    # skipping and processing optional parameters (just "-nocase" for
    # now). When "-nocase" is used, "value" and "words" are translated
    # to lowercase such that the result comparison is not case
    # sensitive.
    #
    set value [string trim [regsub -all -- {[ ]+} ${:value} " "]]
    set firstword [lindex ${:correct_when} 1]
    if {$firstword eq "-nocase" || [string match "*lower*" $firstword]} {
      set value [string tolower $value]
      set words [string tolower [lrange ${:correct_when} 2 end]]
      set modifier nocase
    } else {
      set words [lrange ${:correct_when} 1 end]
      set modifier ""
    }
    return [list op [lindex ${:correct_when} 0] words $words value $value modifier $modifier]
  • readonly (setter)

  • remove_omit (scripted)

    set m ::xowiki::formfield::omit
    if {[:ismixin $m]} {:mixin delete $m}
  • render (scripted)

    #
    # In case, we use an asHTML of a FormField, we use this
    # render definition.
    #
    if {${:inline}} {
      # with label, error message, help text
      :render_form_widget
    } else {
      # without label, error message, help text
      :render_item
    }
    set :__rendered 1
  • render_answer_statistics (scripted)

    #ns_log notice ":answer_statistics: ${:answer_statistics}"
    ::html::ul {
      foreach {answer freq} [lsort -decreasing -integer -stride 2 -index 1 ${:answer_statistics}] {
        html::li { html::t "$freq: $answer" }
      }
    }
  • render_collapsed (scripted)

    ::xowiki::BootstrapCollapseButton new -id $id -label $label -toggle "collapse" -direction "down"
    ::html::div -id "$id" -class "collapse" {
      :$inner_method
    }
  • render_disabled_as_div (scripted)

    set attributes [:get_attributes id]
    lappend attributes class $CSSclass
    ::html::div $attributes {
      if {[info exists :value_with_markup]} {
        ::html::t -disableOutputEscaping ${:value_with_markup}
      } else {
        ::html::t [:value]
      }
    }
  • render_error_msg (scripted)

    #ns_log notice "render_error_msg: field ${:name} has errorMsg (reported [info exists :error_reported]) '${:error_msg}'"
    if {${:error_msg} ne "" && ![info exists :error_reported]} {
      ::html::div -class form-error {
        set label ${:label} ;# needed for error_msg; TODO: we should provide a substitution_list similar to "_"
        ::html::t [::xo::localize ${:error_msg}]
        :render_localizer
        set :error_reported 1
      }
    }
  • render_form_widget (scripted)

    # This method provides the form-widget wrapper
    if {0} {
      # no wrapper for form-widget (label + input)
      :render_input
    } else {
      set CSSclass [expr {[info exists :form_widget_CSSclass] ? ${:form_widget_CSSclass} : ""}]
      if {${:error_msg} ne ""} {
        append CSSclass " form-widget-error"
      }
      set atts [list class $CSSclass]
      if {${:inline}} {
        lappend atts style "display: inline;"
      }
      ::html::div $atts { :render_input }
    }
  • render_help_text (scripted)

    if {${:help_text} ne ""} {
      html::div -class [:form_help_text_CSSclass] {
        html::span -class "info-sign" { }
        html::t ${:help_text}
      }
    }
  • render_input (scripted)

    #
    # This is the most general widget content renderer.
    # If no special renderer is defined, we fall back to this one,
    # which is in most cases  a simple input field of type string.
    #
    set value [:value]
    if {${:mode} ne "edit"} {
      html::t -disableOutputEscaping [:pretty_value $value]
      return
    }
    if {[info exists :validate_via_ajax] && [:validator] ne ""} {
      set ajaxhelper 1
      ::xowiki::Includelet require_YUI_JS -ajaxhelper 0 "yahoo/yahoo-min.js"
      ::xowiki::Includelet require_YUI_JS -ajaxhelper 0 "dom/dom-min.js"
      ::xowiki::Includelet require_YUI_JS -ajaxhelper 0 "event/event-min.js"
      ::xowiki::Includelet require_YUI_JS -ajaxhelper 0 "connection/connection-min.js"
      ::xo::Page requireJS  "/resources/xowiki/yui-form-field-validate.js"
      set package_url [[${:object} package_id] package_url]
      ::xo::Page requireJS  "YAHOO.xo_form_field_validate.add('${:id}','$package_url');"
    }
    
    set booleanAtts [:booleanAttributes required readonly disabled multiple  formnovalidate autofocus]
    #
    # We do not want i18n substitutions in the input fields. So, save
    # away the original value and pass the escaped value to the tDOM
    # renderer.
    #
    set old_value ${:value}
    set :value [:escape_message_keys $old_value]
    ::html::input [:get_attributes type size maxlength id name value style  autocomplete pattern placeholder {CSSclass class} {*}$booleanAtts] {}
    #
    # Reset values to original content
    #
    :resetBooleanAttributes $booleanAtts
    set :value $old_value
    
    :handle_transmit_always $value
    
    set :__rendered 1
  • render_item (scripted)

    ::html::div [:get_attributes {form_item_wrapper_CSSclass class}] {
      if {[:error_msg] ne ""} {
        set CSSclass form-label-error
      } else {
        set CSSclass form-label
      }
      if {[::template::CSS toolkit] eq "bootstrap5"} {
        ::html::label -class "${:form_label_CSSclass} [lindex [split ${:name} .] end]" -for ${:id} {
          ::html::t ${:label}
        }
        if {${:required} && ${:mode} eq "edit"} {
          ::html::div -class form-required-mark {
            ::html::t " (#acs-templating.required#)"
          }
        }
      } else {
        ::html::div -class $CSSclass {
          ::html::label -class "${:form_label_CSSclass} [lindex [split ${:name} .] end]" -for ${:id} {
            ::html::t ${:label}
          }
          if {${:required} && ${:mode} eq "edit"} {
            ::html::div -class form-required-mark {
              ::html::t " (#acs-templating.required#)"
            }
          }
        }
      }
      :render_form_widget
      :render_help_text
      :render_error_msg
      html::t \n
    }
  • render_localizer (scripted)

    # Just an empty fall-back method.
    # This method will be overloaded in trn mode by a mixin.
  • render_modal (scripted)

    ::xowiki::BootstrapCollapseButton new -id $id -label $label -toggle "modal" -direction "down"
    ::html::div -id "$id" -class "modal fade" -tabindex -1 -role dialog aria-hidden "true" {
      ::html::div -class "modal-dialog" -role document {
        ::html::div -class "modal-content" {
          ::html::div -class "modal-header" {
            ::html::h5 -class "modal-title" { ::html::t $label }
            ::html::button -type "button" -class "close" -data-dismiss "modal" -aria-label "Close" {
              ::html::span -aria-hidden "true" { ::html::t -disableOutputEscaping "&times;" }
            }
            ::html::div -class "modal-body" {
              #::html::t ...
              :$inner_method
            }
            ::html::div -class "modal-footer" {
              ::html::button -type "button" -class "btn [::template::CSS class btn-default]" -data-dismiss "modal" {
                ::html::t Close
              }
            }
          }
        }
      }
    }
  • render_result_statistics (scripted)

    #
    # In case, there are result_statistics, use a "progress bar" to
    # visualize correct answers.
    #
    # Currently, this is bootstrap only, unless pie charts are used.
    #
    if {[info exists :result_statistics] && [dict exists ${:result_statistics} count]} {
      set result_count [dict get ${:result_statistics} count]
      #ns_log notice "??? render_result_statistics: ${:name}: ${:result_statistics}"
      if {$result_count > 0} {
        ::html::div -class "progress" {
          set correctCount [expr {[dict exists ${:result_statistics} correct] ? [dict get ${:result_statistics} correct] : 0}]
          set percentage [format %2.0f [expr {$correctCount * 100.0 / $result_count}]]
          ::html::div -class "progress-bar progress-bar-success" -role "progressbar"  -aria-valuenow $percentage -aria-valuemin "0" -aria-valuemax "100" -style "width:$percentage%" {
                ::html::t "$percentage %"
              }
        }
      }
    }
    if {[info exists :answer_statistics]} {
      :render_collapsed  -id answers-[clock clicks -microseconds]  -label "#xowiki.answers#"  -inner_method render_answer_statistics
    }
    if {[info exists :word_statistics]} {
      ns_log notice ":word_statistics: ${:word_statistics}"
      if {${:word_statistics_option} eq "word_cloud"} {
        :render_modal  -id words-[clock clicks -microseconds]  -label "#xowiki.words#"  -inner_method render_word_statistics
      } else {
        #
        # The following is not used for the word cloud, since the
        # placement of the words in the word cloud does not work in
        # collapsed mode.
        #
        :render_collapsed  -id words-[clock clicks -microseconds]  -label "#xowiki.words#"  -inner_method render_word_statistics
      }
    }
  • render_word_statistics (scripted)

    #ns_log notice ":render_word_statistics: ${:word_statistics_option}"
    if {${:word_statistics_option} eq "word_cloud"} {
      # stopword list based on lucene, added a few more terms
      set stopWords {
        a about an and are as at be but by do does for from how if in into is it no not of on or
        such that the their then there these they this to vs was what when where who will with
      }
      set jsWords {}
      foreach {word freq} [lsort -decreasing -integer -stride 2 -index 1 ${:word_statistics}] {
        if {$word in $stopWords} continue
        lappend jsWords [subst {{text: "$word", weight: $freq}}]
      }
      set tsp [clock clicks -microseconds]
      set height [expr {(12*[llength $jsWords]/10)  + 250}]
      # set js [subst {
      #   var jqcloud_$tsp = \[
      #   [join $jsWords ",\n"]
      #   \];
      #   \$('#jqcloud_$tsp').jQCloud(jqcloud_$tsp, {autoResize: true, width: 500, steps: 5, height: $height});
      # }]
      set js [subst {
        var jqcloud_$tsp = \[
        [join $jsWords ",\n"]
        \];
        \$('#jqcloud_$tsp').jQCloud(jqcloud_$tsp, {autoResize: true, width: 500, height: $height});
      }]
      template::add_script -order 20 -src https://cdn.jsdelivr.net/npm/jqcloud2@2.0.3/dist/jqcloud.min.js
      template::head::add_css -href https://cdn.jsdelivr.net/npm/jqcloud2@2.0.3/dist/jqcloud.min.css
      security::csp::require script-src https://cdn.jsdelivr.net
      security::csp::require style-src  https://cdn.jsdelivr.net
    
      template::add_body_script -script $js
      ::html::div -class "jq-cloud" -id jqcloud_$tsp  {}
    } else {
      ::html::ul {
        foreach {word freq} [lsort -decreasing -integer -stride 2 -index 1 ${:word_statistics}] {
          html::li { html::t "$freq: $word" }
          lappend jsWords [subst {{text: "$word", weight: $freq}}]
        }
      }
    }
  • repeat (scripted)

    if {[info exists :__initialized_repeat]} return
    
    set oldClass [:info class]
    :class ::xowiki::formfield::repeatContainer
    
    if {$oldClass ne [:info class]} {
      :reset_parameter
      set :__state reset
    }
    
    if {$range ne ""} {
      if {[regexp {^(\d*)[.][.](\d*)$} $range _ low high]} {
        if {$low ne ""}  {set :min $low}
        if {$high ne ""} {set :max $high}
        if {${:min} > ${:max}} {
          error "invalid range '$range' specified (lower limit ${:min} must not be larger than higher limit ${:max})"
        }
        if {${:min} < 0 || ${:max} < 1} {
          error "invalid range '$range' specified (max ${:max} must be at least 1) "
        }
      } else {
        error "invalid range '$range' specified (must be of form 'min..max')"
      }
    }
    :initialize
  • repeat_add_label (scripted)

    if {[info exists :__initialized_repeat]} return
    set :repeat_add_label $label
  • required (setter)

  • resetBooleanAttributes (scripted)

    #
    # Unset the temporary boolean attributes which were set by method
    # "booleanAttributes".
    #
    foreach att $atts {
      lassign $att var value
      if {[info exists :$var]} {unset :$var}
    }
  • reset_on_validation_error (scripted)

    #
    # We don't actually do anything here, but subclassess can overload it.
    #
  • reset_parameter (scripted)

    # reset application specific parameters (defined below ::xowiki::formfield::FormField)
    # such that searchDefaults will pick up the new defaults, when a form field
    # is reclassed.
    
    if {[info exists :per_object_behavior]} {
      # remove per-object mixin from the "behavior"
      :mixin delete ${:per_object_behavior}
      unset :per_object_behavior
    }
    
    #:msg "reset along [:info precedence]"
    foreach c [:info precedence] {
      if {$c eq "::xowiki::formfield::FormField"} break
      foreach s [$c info slots] {
        if {![$s exists default]} continue
        set var [$s name]
        set key processed($var)
        if {[info exists $key]} continue
        set :$var [$s default]
        set $key 1
      }
    }
    :set_disabled false
  • same_value (scripted)

    if {$v1 eq $v2} {return 1}
    return 0
  • set_disabled (scripted)

    #:msg "${:name} set disabled $disable"
    if {$disable} {
      set :disabled true
    } else {
      unset -nocomplain :disabled
    }
  • set_feedback (scripted)

    #
    # Set instance variables based on correctness of an answer.
    #
    #   - :form_widget_CSSclass
    #   - :evaluated_answer_result
    #   - :value (highlights potentially partial results, e.g. "contains")
    #   - :help_text
    #
    set correct [:answer_is_correct]
    #:log "${:name} [:info class]: correct? $correct"
    switch -- $correct {
      0  { set result "unknown" }
      -1 { set result "incorrect"}
      1  { set result "correct"  }
    }
    :form_widget_CSSclass $result
    set :evaluated_answer_result $result
    
    if {$correct == 0} {
      return ${:evaluated_answer_result}
    }
    
    set feedback ""
    if {[info exists :feedback_answer_$result]} {
      set feedback [set :feedback_answer_$result]
    } else {
      set feedback [_ xowf.answer_$result]
    }
    
    if {$feedback_mode > 1} {
      #ns_log notice "${:name} set_feedback $feedback_mode=[info exists :correct_when] "  "correction?[info exists :correction] "  "correction_data?[info exists :correction_data] "  "============"
      if {[info exists :correct_when]} {
        append feedback ${:correct_when}"
      } elseif {[info exists :correction]} {
        append feedback ${:correction}"
        if {[info exists :correction_data]} {
          #append feedback " ${:correction_data}"
          if {[info exists :grading]} {
            if {${:grading} in  {"" "exact"}} {
              set score [expr {${:evaluated_answer_result} eq "correct" ? 100.0 : 0.0}]
              dict set :correction_data scores ${:grading} $score
            }
            if {[dict exists ${:correction_data} scores ${:grading}] } {
              #
              # We end up here for
              #   - MC (xowiki::formfield::checkbox) and
              #   - SC (xowiki::formfield::radio)
              #   - Reorder (::xowiki::formfield::reorder_box)
              #
              set grading_score [dict get ${:correction_data} scores ${:grading}]
              if {$grading_score < 0} {
                set grading_score 0.0
              }
              #:log "=== ${:name} grading '${:grading}' => $grading_score"
              if {[info exists :test_item_points] && ${:test_item_points} ne ""} {
                set points [format %.2f [expr {${:test_item_points} * $grading_score / 100.0}]]
                dict set :correction_data points $points
                #append feedback " correct: $grading_score "
                append feedback " points: $points of [format %.2f ${:test_item_points}]"
              } else {
                append feedback " grading_score $grading_score"
              }
              #${:object} set_property -new 1 grading_score $grading_score
              set :grading_score $grading_score
              #ns_log notice "=== ${:name} SET GRADING score $grading_score"
            } else {
              ns_log notice "=== ${:name} == no scores for grading '${:grading}': ${:correction_data}"
            }
          } else {
            set :grading_score ""
            ns_log notice "=== ${:name} == no grading available"
          }
        } else {
          ns_log notice "=== ${:name} NO correction_data available"
        }
      } else {
        ns_log notice "=== ${:name} NO correct_when and no :correction"
      }
      #
      # When the widget class supports "disabled_as_div", we
      # can try to highlight matches from :correct_when.
      #
      if {[info exists :disabled_as_div] && [info exists :correct_when]} {
        #
        # When render_as_div might or might not require output
        # escaping. When we have a markup produced form match
        # highlighting, the code sets :value_with_markup for
        # rendering. Otherwise, the plain :value is used.
        #
        #ns_log notice "CHECK matches in ${:name} '${:correct_when}'"
    
        set :value [ns_quotehtml ${:value}]
        set saved_correct_when ${:correct_when}
    
        set op [lindex ${:correct_when} 0]
        if {$op in {contains contains-not}} {
          set :correct_when "AND [list ${:correct_when}]"
          set op AND
        }
        set dicts {}
        if {$op eq "AND"} {
          foreach clause [lrange ${:correct_when} 1 end] {
            set :correct_when $clause
            lappend dicts [:process_correct_when_modifier]
          }
        }
        set :correct_when $saved_correct_when
    
        set annotated_value ${:value}
        #ns_log notice "CHECK matches in ${:name} dicts <$dicts>"
    
        foreach d $dicts {
          if {[dict get $d op] in {contains contains-not}} {
            set CSSclass [dict get $d op]
            #
            # Mark matches in the div element.
            #
            set nocase [expr {[dict get $d modifier] eq "nocase" ? "-nocase" : ""}]
            #ns_log notice "CHECK matches in ${:name} nocase=$nocase words=[dict get $d words]"
    
            foreach word [dict get $d words] {
              #
              # We need here probably more escapes, or we should be more
              # restrictive on allowed content in the "contains" clause.
              #
              set word [string map {* \\* ( \\( ) \\)} $word]
              set nrSubst [regsub -all {*}$nocase -- [ns_quotehtml $word]  $annotated_value  "<span class='match-$CSSclass'>&</span>"  annotated_value ]
              #ns_log notice "MATCH $word -> $nrSubst"
            }
          }
        }
        if {$annotated_value ne ${:value}} {
          set :value_with_markup $annotated_value
        }
      }
    }
    #:log "==== ${:name} setting feedback $feedback"
    set :help_text $feedback
    return ${:evaluated_answer_result}
  • set_is_repeat_template (scripted)

    # :msg "${:name} set is_repeat_template $is_template"
    if {$is_template} {
      set :is_repeat_template true
    } else {
      unset :is_repeat_template
    }
  • show_raw_value (setter)

  • slot (setter)

  • spec (setter)

  • stats_record_count (scripted)

    #
    # This method is just called in situation, where the parent_id is
    # an instantiated object (via answer_is_correct). The parent
    # object is the actual workflow.
    #
    set reporting_obj ::[${:object} parent_id]
    $reporting_obj stats_record_count ${:name}
  • style (setter)

  • td_CSSclass (setter)

  • test_item_in_position (setter)

  • test_item_minutes (setter)

  • test_item_points (setter)

  • title (setter)

  • type (setter)

  • validate (scripted)

    # use the 'value' method to deal e.g. with compound fields
    set value [:value]
    #:msg "[:info class] value=$value req=${:required} // ${:value} //"
    
    if {${:required}
        && $value eq ""
        && ![:istype ::xowiki::formfield::hidden]
      } {
      return [_ acs-templating.Element_is_required [list label ${:label}]]
    }
    #
    #:log "++ ${:name} [:info class] validator=[:validator] ([llength [:validator]]) value=$value"
    foreach validator [:validator] {
      set errorMsg ""
      #
      # The validator might set the variable errorMsg in this scope.
      #
      set success 1
      set validator_method check=$validator
      set proc_info [:procsearch $validator_method]
      #:log "++ ${:name}: field-level validator exists '$validator_method' ? [expr {$proc_info ne {}}]"
      if {$proc_info ne ""} {
        #
        # We have a slot checker, call it.
        #
        #:msg "++ call-field level validator $validator_method '$value'"
        set success [:validation_check $validator_method $value]
      }
      #:log "++ ${:name} [:info class] validator=[:validator] ([llength [:validator]]) value=$value -> $success"
      if {$success == 1} {
        #
        # The previous check was ok, check now for a validator on the
        # object level.
        #
        set validator_method validate=$validator
        set proc_info [$obj procsearch $validator_method]
        #:log "++ ${:name}: page-level validator exists ? [expr {$proc_info ne {}}]"
        if {$proc_info ne ""} {
          set success [$obj $validator_method $value]
          #:msg "++ call page-level validator $validator_method '$value' returns $success"
        }
      }
      if {$success == 0} {
        #
        # We have an error message. Get the class name from procsearch and construct
        # a message key based on the class and the name of the validator.
        #
        set cl [namespace tail [lindex $proc_info 0]]
        #:msg "__langPkg?[info exists __langPkg]"
        if {![info exists __langPkg]} {
          set __langPkg "xowiki"
        }
        #:log "calling $__langPkg.$cl-validate_$validator with [list value $value errorMsg $errorMsg] on level [info level]"
        set msg [_ $__langPkg.$cl-validate_$validator [list value $value errorMsg $errorMsg]]
        if {$msg eq ""} {
          set msg  "validation of field '$validator' failed, '$errorMsg', no message key '$__langPkg.$cl-validate_$validator' provided"
          :log "++ ${:name}: VALIDATION FAILED <$msg>"
        }
        return $msg
      }
    }
    return ""
  • validate_via_ajax (setter)

  • validation_check (scripted)

    return [:uplevel [list :$validator_method $value]]
  • validator (setter)

  • value (setter)

  • value_if_nothing_is_returned_from_form (scripted)

    return $default
  • word_statistics (scripted)

    #
    # Word statistics based on :value. It is assumed here, that the
    # value is basically a string with whitespace.
    #
    regsub -all -- {\s} ${:value} " " value
    foreach w [split $value " "] {
      dict incr :word_statistics [string tolower $w]
    }
    set :word_statistics_option $flavor