Index: openacs-4/packages/xowiki/tcl/form-field-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xowiki/tcl/form-field-procs.tcl,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xowiki/tcl/form-field-procs.tcl 13 Sep 2012 16:05:27 -0000 1.219 @@ -0,0 +1,3481 @@ +::xo::library doc { + XoWiki - form fields + + @creation-date 2007-06-22 + @author Gustaf Neumann + @cvs-id $Id: form-field-procs.tcl,v 1.219 2012/09/13 16:05:27 victorg Exp $ +} + +namespace eval ::xowiki::formfield { + + # Second approximation for form fields. + # FormFields are objects, which can be outputed as well in ad_forms + # or asHTML included in wiki pages. FormFields support + # + # - validation + # - help_text + # - error messages + # - internationlized pretty_values + # + # and inherit properties of the original datatypes via slots + # (e.g. for boolean entries). FormFields can be subclassed + # to ensure tailorability and high reuse. + # + # todo: at some later time, this could go into xotcl-core + + ########################################################### + # + # ::xowiki::FormField (Base Class) + # + ########################################################### + Class create FormField -superclass ::xo::tdom::Object -parameter { + {required false} + {display_field true} + {hide_value false} + {inline false} + {disabled} + {show_raw_value} + CSSclass + style + {form_widget_CSSclass form-widget} + {form_item_wrapper_CSSclass form-item-wrapper} + {type text} + {label} + {name} + {id} + {value ""} + {spec ""} + {help_text ""} + {error_msg ""} + {validator ""} + {validate_via_ajax} + + {autocomplete} + {autofocus} + {formnovalidate} + {multiple} + {pattern} + {placeholder} + {readonly} + + locale + default + object + slot + answer + correct_when + feedback_answer_correct + feedback_answer_incorrect + } + FormField set abstract 1 + + FormField proc fc_encode {string} { + return [string map [list , __COMMA__] $string] + } + FormField proc fc_decode {string} { + return [string map [list __COMMA__ ,] $string] + } + #FormField proc fc_decode_colon {string} { + # return [string map [list __COLON__ :] $string] + #} + + FormField proc get_from_name {object name} { + # + # 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]} { + my msg "strange, $f [$f name] was created without object but fits name" + return $f + } elseif {$object eq [$f object]} { + return $f + } + } + } + #my msg not-found-$object-$name + return "" + } + + + FormField instproc init {} { + if {![my exists label]} {my label [string totitle [my name]]} + if {![my exists id]} {my id [my name]} + if {[my exists id]} {my set html(id) [my id]} + #if {[my exists default]} {my set value [my default]} + my config_from_spec [my spec] + } + + # + # Basic initialze method, doing nothing; should be subclassed by the + # application classes + FormField instproc initialize {} {next} + + FormField instproc get_json {} { + return [util_spec2json [list [my get_spec]]] + } + + FormField instproc get_spec {} { + set pairs [list [list CSSclass class]] + # Special handling of HTML boolean attributes, since they require a + # different coding; it would be nice, if tdom would care for this. + set booleanAtts [list required readonly disabled multiple formnovalidate autofocus] + foreach att $booleanAtts { + if {[my exists $att] && [my set $att]} { + my set __#$att $att + lappend pairs [list __#$att $att] + } + } + + set atts [eval my get_attributes type size maxlength id name value \ + pattern placeholder $pairs] + + foreach att $booleanAtts { + if {[my exists __#$att]} {my unset __#$att} + } + + return [list "input" $atts {}] + } + + FormField instproc validation_check {validator_method value} { + return [my $validator_method $value] + } + + FormField instproc validate {obj} { + my instvar name required + + # use the 'value' method to deal e.g. with compound fields + set value [my value] + #my msg "[my info class] value=$value req=$required // [my set value] //" + + if {$required && $value eq "" && ![my istype ::xowiki::formfield::hidden]} { + my instvar label + return [_ acs-templating.Element_is_required] + } + # + #my msg "++ [my name] [my info class] validator=[my validator] ([llength [my validator]]) value=$value" + foreach validator [my validator] { + set errorMsg "" + # + # The validator might set the variable errorMsg in this scope. + # + set success 1 + set validator_method check=$validator + set proc_info [my procsearch $validator_method] + #my msg "++ [my name]: field-level validator exists '$validator_method' ? [expr {$proc_info ne {}}]" + if {$proc_info ne ""} { + # we have a slot checker, call it + #my msg "++ call-field level validator $validator_method '$value'" + set success [my validation_check $validator_method $value] + } + 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] + #my msg "++ [my name]: page-level validator exists ? [expr {$proc_info ne {}}]" + if {$proc_info ne ""} { + set success [$obj $validator_method $value] + #my 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]] + return [_ xowiki.$cl-validate_$validator [list value $value errorMsg $errorMsg]] + #return [::lang::message::lookup "" xowiki.$cl-validate_$validator %errorMsg% [list value $value errorMsg $errorMsg] 1] + } + } + return "" + } + + FormField instproc reset_parameter {} { + # 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 {[my exists per_object_behavior]} { + # remove per-object mixin from the "behavior" + my mixin delete [my set per_object_behavior] + my unset per_object_behavior + } + + #my msg "reset along [my info precedence]" + foreach c [my 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 + my set $var [$s default] + set $key 1 + } + } + if {[my exists disabled]} { + my set_disabled 0 + } + } + + FormField proc interprete_condition {-package_id -object cond} { + 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 0 + } + return $success + } + + FormField set cond_regexp {^([^=?]+)[?]([^:]*)[:](.*)$} + + FormField proc get_single_spec {-package_id -object string} { + if {[regexp [my set cond_regexp] $string _ condition true_spec false_spec]} { + if {[my interprete_condition -package_id $package_id -object $object $condition]} { + return [my get_single_spec -package_id $package_id -object $object $true_spec] + } else { + return [my get_single_spec -package_id $package_id -object $object $false_spec] + } + } + return $string + } + + FormField instproc remove_omit {} { + set m ::xowiki::formfield::omit + if {[my ismixin $m]} {my mixin delete $m} + } + FormField instproc set_disabled {disable} { + #my msg "[my name] set disabled $disable" + if {$disable} { + my set disabled true + } else { + my unset -nocomplain disabled + } + } + + FormField instproc behavior {mixin} { + + # + # Specify the behavior of a form field via + # per object mixins + # + set obj [my object] + set pkgctx [[$obj package_id] context] + if {[$pkgctx exists embedded_context]} { + set ctx [$pkgctx set embedded_context] + set classname ${ctx}::$mixin + #my msg ctx=$ctx-viewer=$mixin,found=[my isclass $classname] + # TODO: search different places for the mixin. Special namespace? + if {[my isclass $classname]} { + if {[my exists per_object_behavior]} { + my mixin delete [my set per_object_behavior] + } + my mixin add $classname + my set per_object_behavior $classname + } else { + my msg "Could not find mixin '$mixin'" + } + } + } + + FormField instproc repeatable {} { + my mixin add ::xowiki::formfield::repeatable + my reset_parameter + } + + FormField instproc interprete_single_spec {s} { + if {$s eq ""} return + + set object [my object] + 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 {my set required false} + required {my set required true; my remove_omit} + omit {my mixin add ::xowiki::formfield::omit} + repeatable {my repeatable} + noomit {my remove_omit} + disabled {my set_disabled true} + enabled {my set_disabled false} + label=* {my label [lindex [split $s =] 1]} + help_text=* {my help_text [lindex [split $s =] 1]} + *=* { + set p [string first = $s] + set attribute [string range $s 0 [expr {$p-1}]] + set value [string range $s [expr {$p+1}] end] + set definition_class [lindex [my procsearch $attribute] 0] + set method [my info methods $attribute] + if {[string match "::xotcl::*" $definition_class] || $method eq ""} { + error [_ xowiki.error-form_constraint-unknown_attribute [list class [my info class] name [my name] entry $attribute]] + } + if {[catch { + # + # 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] + } + my $attribute $value + } errMsg]} { + error "Error during setting attribute '$attribute' to value '$value': $errMsg" + } + } + default { + # Check, if the spec value $s is a class. + set old_class [my info class] + # Don't allow to use namespaced values, since we would run + # into a recursive loop for richtext::wym (could be altered there as well). + if {[my isclass ::xowiki::formfield::$s] && ![string match "*:*" $s]} { + my class ::xowiki::formfield::$s + my remove_omit + if {$old_class ne [my info class]} { + #my msg "[my name]: reset class from $old_class to [my info class]" + my reset_parameter + my set __state reset + my initialize + } + } else { + if {$s ne ""} { + error [_ xowiki.error-form_constraint-unknown_spec_entry \ + [list name [my name] entry $s x "Unknown spec entry for entry '$s'"]] + } + } + } + } + } + + FormField instproc config_from_spec {spec} { + #my log "spec=$spec [my info class] [[my info class] exists abstract]" + + my instvar type + if {[[my info class] exists abstract]} { + # had earlier here: [my info class] eq [self class] + # Check, wether the actual class is a concrete class (mapped to + # concrete field type) or an abstact class. Since + # config_from_spec can be called multiple times, we want to do + # the reclassing only once. + if {[my isclass ::xowiki::formfield::$type]} { + my class ::xowiki::formfield::$type + } else { + my class ::xowiki::formfield::text + } + # set missing instance vars with defaults + my set_instance_vars_defaults + } + regsub -all {,\s+} $spec , spec + foreach s [split $spec ,] { + my interprete_single_spec [FormField fc_decode $s] + } + + #my msg "[my name]: after specs" + my set __state after_specs + my 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 {[my value] eq "" && [my exists default] && [my default] ne ""} { + #my msg "+++ reset value to [my default]" + my value [my default] + } + + if {[lang::util::translator_mode_p]} { + my mixin add "::xo::TRN-Mode" + } + + } + + FormField instproc asWidgetSpec {} { + my instvar widget_type options label help_text format html display_html + set spec $widget_type + if {[my exists spell]} {append spec ",[expr {[my spell] ? {} : {no}}]spell"} + + if {![my required]} {append spec ",optional"} + append spec " {label " [list $label] "} " + + if {[my exists html]} { + append spec " {html {" + foreach {key value} [array get html] { + append spec $key " " [list $value] " " + } + append spec "}} " + } + + if {[my exists options]} { + append spec " {options " [list $options] "} " + } + if {[my exists format]} { + append spec " {format " [list $format] "} " + } + + if {$help_text ne ""} { + if {[string match "#*#" $help_text]} { + set internationalized [my localize $help_text] + append spec " {help_text {$internationalized}}" + } else { + append spec " {help_text {$help_text}}" + } + } + return $spec + } + + FormField instproc render {} { + # In case, we use an asHTML of a FormField, we use this + # render definition + if {[my inline]} { + # with label, error message, help text + my render_form_widget + } else { + # without label, error message, help text + my render_item + } + my set __rendered 1 + } + + FormField instproc render_form_widget {} { + # This method provides the form-widget wrapper + set CSSclass [my form_widget_CSSclass] + if {[my error_msg] ne ""} {append CSSclass " form-widget-error"} + set atts [list class $CSSclass] + if {[my inline]} {lappend atts style "display: inline;"} + ::html::div $atts { my render_input } + } + + FormField instproc render_input {} { + # + # 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 fied of type string. + # + if {[my exists validate_via_ajax] && [my 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 [[[my object] package_id] package_url] + ::xo::Page requireJS "YAHOO.xo_form_field_validate.add('[my id]','$package_url');" + } + + #::html::input [eval my get_attributes type size maxlength id name value \ + # pattern placeholder $pairs] {} + util_createDom [list [my get_spec]] + + # + # Disabled fieds 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 {[my exists disabled] && [my exists transmit_field_always]} { + ::html::input [list type hidden name [my name] value [my set value]] {} + } + my set __rendered 1 + } + + FormField instproc render_item {} { + ::html::div -class [my form_item_wrapper_CSSclass] { + if {[my error_msg] ne ""} { + set CSSclass form-label-error + } else { + set CSSclass form-label + } + ::html::div -class $CSSclass { + ::html::label -for [my id] { + ::html::t [my label] + } + if {[my required]} { + ::html::div -class form-required-mark { + ::html::t " (#acs-templating.required#)" + } + } + } + my render_form_widget + my render_help_text + my render_error_msg + html::t \n + } + } + + FormField instproc render_error_msg {} { + if {[my error_msg] ne "" && ![my exists error_reported]} { + ::html::div -class form-error { + my instvar label + ::html::t [::xo::localize [my error_msg]] + my render_localizer + my set error_reported 1 + } + } + } + + FormField instproc render_help_text {} { + set text [my help_text] + if {$text ne ""} { + html::div -class form-help-text { + html::img -src "/shared/images/info.gif" -alt {[i]} -title {Help text} \ + -width "12" -height 9 -border 0 -style "margin-right: 5px" {} + html::t $text + } + } + } + + FormField instproc render_localizer {} { + # Just an empty fall-back method. + # This method will be overloaded in trn mode by a mixin. + } + + FormField instproc localize {v} { + # 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 [my locale] $key] + } + return $v + } + + FormField instproc value_if_nothing_is_returned_from_form {default} { + return $default + } + + FormField instproc pretty_value {v} { + #my log "mapping $v" + return [string map [list & "&" < "<" > ">" \" """ ' "'" @ "@"] $v] + } + + FormField instproc has_instance_variable {var value} { + if {[my exists $var] && [my set $var] eq $value} {return 1} + return 0 + } + FormField instproc convert_to_internal {} { + # to be overloaded + } + FormField instproc convert_to_external {value} { + # to be overloaded + return $value + } + + FormField instproc answer_check=eq {} { + my instvar value + set arg1 [lindex [my correct_when] 1] + return [expr {$value eq $arg1}] + } + FormField instproc answer_check=gt {} { + my instvar value + set arg1 [lindex [my correct_when] 1] + return [expr {$value > $arg1}] + } + FormField instproc answer_check=ge {} { + my instvar value + set arg1 [lindex [my correct_when] 1] + return [expr {$value >= $arg1}] + } + FormField instproc answer_check=lt {} { + my instvar value + set arg1 [lindex [my correct_when] 1] + return [expr {$value < $arg1}] + } + FormField instproc answer_check=le {} { + my instvar value + set arg1 [lindex [my correct_when] 1] + return [expr {$value <= $arg1}] + } + FormField instproc answer_check=btwn {} { + my instvar value + set arg1 [lindex [my correct_when] 1] + set arg2 [lindex [my correct_when] 2] + return [expr {$value >= $arg1 && $value <= $arg2}] + } + FormField instproc answer_check=in {} { + my instvar value + set values [lrange [my correct_when] 1 end] + return [expr {[lsearch -exact $values $value] > -1}] + } + FormField instproc answer_check=match {} { + return [string match [lindex [my correct_when] 1] [my value]] + } + FormField instproc answer_check=answer_words {} { + set value [regsub -all { +} [my value] " "] + if {[string match "*lower*" [lindex [my correct_when] 1]]} { + set value [string tolower $value] + } + return [expr {$value eq [my answer]}] + } + + FormField instproc answer_is_correct {} { + #my msg "[my name] ([my info class]): value=[my value], answer=[expr {[my exists answer]?[my set answer]:{NONE}}]" + if {[my exists correct_when]} { + set op [lindex [my correct_when] 0] + if {[my procsearch answer_check=$op] ne ""} { + set r [my answer_check=$op] + if {$r == 0} {return -1} {return 1} + } else { + error "invalid operator '$op'" + } + } elseif {![my exists answer]} { + return 0 + } elseif {[my value] ne [my answer]} { + #my msg "v='[my value]' NE a='[my answer]'" + return -1 + } else { + return 1 + } + } + + FormField instproc field_value {v} { + if {[my exists show_raw_value]} { + return $v + } else { + return [my pretty_value] + } + } + + FormField instproc pretty_image {-parent_id:required entry_name} { + if {$entry_name eq ""} return + if {[my set value] eq ""} return + my instvar object value + + array set "" [$object item_ref -default_lang [$object lang] -parent_id $parent_id $entry_name] + + set label [my label] ;# the label is used for alt und title + if {$label eq $(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 [[my object] title] + } + + set l [::xowiki::Link create new -destroy_on_cleanup \ + -page $object -type "image" -lang $(prefix) \ + [list -stripped_name $(stripped_name)] [list -label $label] \ + -parent_id $(parent_id) -item_id $(item_id)] + + if {[my istype file]} { + set revision_id [my get_from_value $value revision_id] + 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 botton left right + geometry + } { + if {[my exists $option]} {$l set $option [my set $option]} + } + set html [$l render] + return $html + } + + ########################################################### + # + # helper method for extending slots: + # either, we make a meta class for form-fields, or this should + # should go into xotcl-core + # + ########################################################### + + ::Serializer exportMethods { + ::xotcl::Class instproc extend_slot + } + Class instproc extend_slot {name value} { + # create a mirroring slot and add the specified value to the default + foreach c [my info heritage] { + if {[info command ${c}::slot::$name] ne ""} { + set value [concat $value [${c}::slot::$name default]] + break + } + } + my slots [list Attribute create validator -default $value] + } + + ########################################################### + # + # ::xowiki::formfield::submit_button + # + ########################################################### + + Class submit_button -superclass FormField + submit_button instproc initialize {} { + my set type submit + my set value [::xo::localize [_ xowiki.Form-submit_button]] + } + submit_button instproc render_input {} { + # don't disable submit buttons + if {[my type] eq "submit"} {my unset -nocomplain disabled} + ::html::input [my get_attributes name type {CSSclass class} value disabled] {} + my render_localizer + } + + ########################################################### + # + # ::xowiki::formfield::file + # + ########################################################### + + Class create file -superclass FormField -parameter { + {size 40} + {sticky false} + link_label + } + file instproc tmpfile {value} {my set [self proc] $value} + file instproc content-type {value} {my set [self proc] $value} + file instproc initialize {} { + my type file + my set widget_type file(file) + next + } + file instproc entry_info {value} { + return [list name file:[my name] parent_id [[my object] item_id]] + } + + file instproc get_value_from_form {} { + set old_value [[my object] form_parameter __old_value_[my name] ""] + set v [my set value] + #my msg "value '$v' // old_value '$old_value'" + # + # Figure out, if we got a different file-name (value). If the + # file-name is the same as in the last revision, we return a + # "-". This has the effect, that file file is not uploaded again. + # + #if {$old_value ne "" && $old_value eq [my set value]} {} + + if {$old_value ne "" && $v eq ""} { + return "-" + } + return $v + } + + file instproc get_from_value {value attribute {raw ""}} { + # + # The value of of a form entry might be: + # - an atomic list element + # - a list with attribute value pairs + # + # This function tries to obtain the queried attribute from the + # attribute value pair notation. If this fails, it returns a + # default value. + # + set valueLength [llength $value] + if {$valueLength > 1 && $valueLength %2 == 0} { + array set "" $value + if {[info exists ($attribute)]} { + return $($attribute) + } + } + return [lindex $raw 0] + } + + file instproc convert_to_internal {} { + my instvar value + + set v [my get_value_from_form] + if {$v eq "-" || $v eq ""} { + # nothing to do, keep the old value + #my msg "nothing to do with '$v'" + set value [[my object] form_parameter __old_value_[my name] ""] + [my object] set_property [my name] $value + return + } + regsub -all {\\+} $value {/} value ;# fix IE upload path + set value [::file tail $value] + [my object] set_property [my name] $value + + set package_id [[my object] package_id] + array set entry_info [my entry_info $value] + + set content_type [my set content-type] + if {$content_type eq "application/octetstream" + || $content_type eq "application/force-download" + } { + set content_type [::xowiki::guesstype $value] + } + #my msg "mime_type of $entry_info(name) = [::xowiki::guesstype $value] // [my set content-type] ==> $content_type" + set file_object [$package_id get_page_from_name -name $entry_info(name) -parent_id $entry_info(parent_id)] + if {$file_object ne ""} { + # file entry exists already, create a new revision + #my msg "new revision (value $value)" + $file_object set import_file [my set tmpfile] + $file_object set mime_type $content_type + $file_object set title $value + $file_object save + # + # Update the value with the attribute value pair list containing + # the revision_id. TODO: clear revision_id on export. + # + [my object] set_property -new 1 [my name] [list name $value revision_id [$file_object revision_id]] + } else { + # create a new file + #my msg "new file" + set file_object [::xowiki::File new -destroy_on_cleanup \ + -title $value \ + -name $entry_info(name) \ + -parent_id $entry_info(parent_id) \ + -mime_type $content_type \ + -package_id [[my object] package_id] \ + -creation_user [::xo::cc user_id] ] + $file_object set import_file [my set tmpfile] + $file_object save_new + # Make sure the value is just one list item + [my object] set_property -new 1 [my name] [list $value] + } + } + + file instproc label_or_value {v} { + if {[my exists link_label]} { + return [my localize [my link_label]] + } + return $v + } + + file instproc pretty_value {v} { + if {$v ne ""} { + my instvar object + array set "" [my entry_info $v] + array set "" [$object item_ref -default_lang [[my object] lang] -parent_id $(parent_id) $(name)] + #my msg "pretty value name '$(stripped_name)'" + set l [::xowiki::Link create new -destroy_on_cleanup \ + -page $object -type "file" -lang $(prefix) \ + [list -stripped_name $(stripped_name)] [list -label [my label]] \ + [list -extra_query_parameter [list [list filename [my get_from_value $v name $v]]]] \ + -parent_id $(parent_id) -item_id $(item_id)] + return [$l render] + } + } + + file instproc render_input {} { + util_createDom [list [my get_spec]] + } + + file instproc get_spec {} { + my instvar value + set package_id [[my object] package_id] + array set entry_info [my entry_info $value] + set fn [my get_from_value $value name $value] + set href [$package_id pretty_link -download 1 -parent_id $entry_info(parent_id) $entry_info(name)] + if {![my istype image]} { + append href ?filename=[ns_urlencode $fn] + } + # + # The HTML5 handling of "required" would force us to upload in + # every form the file again. To implement the sticky option, we + # set temporarily the "required" attribute to false + # + if {[my exists required]} { + set reset_required 1 + my set required false + } + + lassign [next] tag atts children + + if {[info exists reset_required]} { + my set required true + } + + set additional_spec [util_tdom2list { + # FOLLOWING GIVES TROUBLE, SEE util_spec2json FOR DETAILS + ::html::t " " + set id __old_value_[my name] + ::html::input -type hidden -name $id -id $id -value $value + #my msg "old_value '$value'" + ::html::span -class file-control -id __a$id { + ::html::a -href $href {::html::t [my label_or_value $fn] } + # Show the clear button just when + # - there is something to clear, and + # - the formfield is not disabled, and + # - the form-field is not sticky (default) + set disabled [expr {[my exists disabled] && [my disabled] ne "false"}] + if {$value ne "" && !$disabled && ![my sticky] } { + ::html::input -type button -value clear \ + -onClick "document.getElementById('$id').value = ''; document.getElementById('__a$id').style.display = 'none';" + } + } + }] + + lappend children $additional_spec + return [list $tag $atts $children] + } + + ########################################################### + # + # ::xowiki::formfield::import_archive + # + ########################################################### + + Class import_archive -superclass file -parameter { + {cleanup false} + } + import_archive instproc initialize {} { + next + if {[my help_text] eq ""} {my help_text "#xowiki.formfield-import_archive-help_text#"} + } + import_archive instproc pretty_value {v} { + my instvar object + set package_id [$object package_id] + set parent_id [$object parent_id] + if {$v eq ""} {return ""} + array set "" [my entry_info $v] + set fn [my get_from_value $v name $v] + # + # Get the file object of the imported file to obtain is full name and path + # + set file_id [$package_id lookup -parent_id [$object item_id] -name $(name)] + ::xo::db::CrClass get_instance_from_db -item_id $file_id + set full_file_name [$file_id full_file_name] + # + # Call the archiver to unpack and handle the archive + # + set f [::xowiki::ArchiveFile new -file $full_file_name -name $fn -parent_id $parent_id] + if {[$f unpack]} { + # + # So, all the hard work is done. We take a hard measure here to + # cleanup the entry in case everything was imported + # successful. Note that setting "cleanup" without thought might + # lead to maybe unexpected deletions of the form-page + # + if {[my cleanup]} { + set return_url [$package_id query_parameter "return_url" [$parent_id pretty_link]] + $package_id returnredirect [export_vars -base [$object pretty_link] [list {m delete} return_url]] + } + } + } + + ########################################################### + # + # ::xowiki::formfield::image + # + ########################################################### + + Class image -superclass file -parameter { + 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 botton left right + } + image instproc pretty_value {v} { + array set "" [my entry_info $v] + + return [my pretty_image -parent_id $(parent_id) $(name)] + } + + ########################################################### + # + # ::xowiki::formfield::hidden + # + ########################################################### + + Class hidden -superclass FormField + hidden instproc initialize {} { + my type hidden + my set widget_type text(hidden) + # remove mixins in case of retyping + my mixin "" + } + hidden instproc render_item {} { + # don't render the labels + my render_form_widget + } + hidden instproc render_help_text {} { + } + + ########################################################### + # + # ::xowiki::formfield::omit + # + ########################################################### + + Class omit -superclass FormField + omit instproc render_item {} { + # don't render the labels + #my render_form_widget + } + omit instproc render_help_text {} { + } + + ########################################################### + # + # ::xowiki::formfield::inform + # + ########################################################### + + Class inform -superclass FormField + inform instproc initialize {} { + my type hidden + my set widget_type text(inform) + } + inform instproc render_input {} { + ::html::t [my value] + ::html::input [my get_attributes type id name value disabled {CSSclass class}] {} + } + inform instproc render_help_text {} { + } + + ########################################################### + # + # ::xowiki::formfield::text + # + ########################################################### + + Class text -superclass FormField -parameter { + {size 80} + maxlength + } + text instproc initialize {} { + my type text + my set widget_type text + foreach p [list size maxlength] {if {[my exists $p]} {my set html($p) [my $p]}} + } + text instproc get_spec {} { + set atts [my get_attributes type size maxlength id name value \ + pattern placeholder] + + return [list input $atts {}] + } + + ########################################################### + # + # ::xowiki::formfield::color + # + ########################################################### + + Class color -superclass text + color instproc initialize {} { + next + my type color + } + + ########################################################### + # + # ::xowiki::formfield::datetime + # + ########################################################### + + Class datetime -superclass text + datetime instproc initialize {} { + next + my type datetime + } + # names for HTML5 types + # date, month + # already in use, should redefine accordingly when avail + + ########################################################### + # + # ::xowiki::formfield::datetime-local + # + ########################################################### + + Class datetime-local -superclass text + datetime-local instproc initialize {} { + next + my type datetime-local + } + + ########################################################### + # + # ::xowiki::formfield::time + # + ########################################################### + + Class time -superclass text + time instproc initialize {} { + next + my type time + } + + ########################################################### + # + # ::xowiki::formfield::week + # + ########################################################### + + Class week -superclass text + week instproc initialize {} { + next + my type datetime + } + + ########################################################### + # + # ::xowiki::formfield::email + # + ########################################################### + + Class email -superclass text + email instproc initialize {} { + next + my type email + } + + ########################################################### + # + # ::xowiki::formfield::search + # + ########################################################### + + Class search -superclass text + search instproc initialize {} { + next + my type search + } + ########################################################### + # + # ::xowiki::formfield::tel + # + ########################################################### + + Class tel -superclass text + tel instproc initialize {} { + next + my type tel + } + + ########################################################### + # + # ::xowiki::formfield::number + # + ########################################################### + + Class number -superclass FormField -parameter { + min max step value + } + number instproc initialize {} { + my type number + my set widget_type text + } + number instproc render_input {} { + ::html::input [my get_attributes type id name value disabled {CSSclass class} min max step value \ + autofocus formnovalidate multiple pattern placeholder readonly required] {} + } + + ########################################################### + # + # ::xowiki::formfield::range + # + ########################################################### + + Class range -superclass FormField -parameter { + min max step value + } + range instproc initialize {} { + my type range + my set widget_type text + } + range instproc render_input {} { + ::html::input [my get_attributes type id name value disabled {CSSclass class} min max step value \ + autofocus formnovalidate multiple pattern placeholder readonly required] {} + } + + + ########################################################### + # + # ::xowiki::formfield::password + # + ########################################################### + + Class password -superclass text + password instproc initialize {} { + next + my set widget_type password + my type password + } + ########################################################### + # + # ::xowiki::formfield::numeric + # + ########################################################### + + Class numeric -superclass text -parameter { + {format %.2f} + } -extend_slot validator numeric + numeric instproc initialize {} { + next + my set widget_type numeric + # check, if we we have an integer format + my set is_integer [regexp {%[0-9.]*d} [my format]] + } + numeric instproc convert_to_external value { + if {$value ne ""} { + if { [catch "lc_numeric $value [my format] [my locale]" result] } { + util_user_message -message "[my label]: $result (locale=[my locale])" + #my msg [list lc_numeric $value [my format] [my locale]] + set converted_value $value + if {[catch {scan $value [my format] converted_value}]} { + return $value + } else { + return $converted_value + } + } + return $result + } + return $value + } + numeric instproc convert_to_internal {} { + if {[my value] ne ""} { + set value [lc_parse_number [my value] [my locale] [my set is_integer]] + [my object] set_property -new 1 [my name] [expr {$value}] + return + } + } + numeric instproc check=numeric {value} { + return [expr {[catch {lc_parse_number $value [my locale] [my set is_integer]}] == 0}] + } + numeric instproc pretty_value value { + return [my convert_to_external $value] + } + numeric instproc answer_check=eq {} { + # use numeric equality + return [expr {[my value] == [lindex [my correct_when] 1]}] + } + + ########################################################### + # + # ::xowiki::formfield::user_id + # + ########################################################### + + Class user_id -superclass numeric -parameter { + {format %d} + } + user_id instproc initialize {} { + next + my set is_party_id 1 + } + user_id instproc pretty_value {v} { + return [::xo::get_user_name $v] + } + + ########################################################### + # + # ::xowiki::formfield::author + # + ########################################################### + + Class author -superclass user_id -parameter { + {photo_size 54} + {with_photo true} + {with_user_link false} + {label #xowiki.formfield-author#} + } + author instproc pretty_value {v} { + if {$v ne ""} { + my instvar object + acs_user::get -user_id $v -array user + if {[my with_photo]} { + set portrait_id [acs_user::get_portrait_id -user_id $v] + if {$portrait_id == 0} { + package require md5 + set md5 [string tolower [md5::Hex [md5::md5 -- $user(email)]]] + set src http://www.gravatar.com/avatar/$md5?size=[my photo_size]&d=mm + } else { + set src "/shared/portrait-bits.tcl?user_id=$v" + } + set photo "" + set photo_class "photo" + } else { + set photo "" + set photo_class "" + } + set date_field [::xowiki::FormPage get_table_form_fields \ + -base_item $object \ + -field_names _last_modified \ + -form_constraints ""] + set date [$date_field pretty_value [$object property _last_modified]] + + if {[my with_user_link]} { + set user_link_begin "" + set user_link_end "" + } else { + set user_link_begin "" + set user_link_end "" + } + + return [subst { +
$photo +

$user_link_begin$user(first_names) $user(last_name)$user_link_end

+

$date

+
+ }] + } + return "" + } + + ########################################################### + # + # ::xowiki::formfield::party_id + # + ########################################################### + + Class party_id -superclass user_id \ + -extend_slot validator party_id_check + party_id instproc check=party_id_check {value} { + if {$value eq ""} {return 1} + return [db_0or1row [my qn check_party] "select 1 from parties where party_id = :value"] + } + + ########################################################### + # + # ::xowiki::formfield::url + # + ########################################################### + + Class url -superclass text -parameter { + {link_label} + } + url instproc initialize {} { + next + my type url + } + url instproc pretty_value {v} { + if {$v ne ""} { + if {[my exists link_label]} { + set link_label [my localize [my link_label]] + } else { + set link_label $v + } + regsub -all & $v "&" v + return "$link_label" + } + } + + ########################################################### + # + # ::xowiki::formfield::detail_link + # + ########################################################### + + Class detail_link -superclass url -parameter { + {link_label "#xowiki.weblog-more#"} + } + detail_link instproc pretty_value {v} { + if {$v eq ""} { + return "" + } + if {$v ne ""} { + set link_label [my localize [my link_label]] + regsub -all & $v "&" v + return " \[ $link_label \]" + } + } + + ########################################################### + # + # ::xowiki::formfield::textarea + # + ########################################################### + + Class textarea -superclass FormField -parameter { + {rows 2} + {cols 80} + {spell false} + } + textarea instproc initialize {} { + my set widget_type text(textarea) + foreach p [list rows cols style] {if {[my exists $p]} {my set html($p) [my $p]}} + if {![my istype ::xowiki::formfield::richtext] && [my exists editor]} { + # downgrading + #my msg "downgrading [my info class]" + foreach m [my info mixin] {if {[$m exists editor_mixin]} {my mixin delete $m}} + foreach v {editor options} {if {[my exists $v]} {my unset $v}} + } + next + } + + textarea instproc render_input {} { + ::html::textarea [my get_attributes id name cols rows style {CSSclass class} disabled] { + ::html::t [my value] + } + } + + ########################################################### + # + # ::xowiki::formfield::code_listing + # + ########################################################### + + Class code_listing -superclass textarea -parameter { + {rows 20} + {cols 80} + } + code_listing instproc pretty_value {v} { + [my object] do_substitutions 0 + if {[info command api_tclcode_to_html] ne ""} { + set html [api_tclcode_to_html [my value]] + regsub -all "\n?\r" $html html + return "
$html
" + } else { + return "
[string map [list & {&} < {<} > {>}]  [my value]]
" + } + } + + ########################################################### + # + # ::xowiki::formfield::richtext + # + ########################################################### + + Class richtext -superclass textarea \ + -extend_slot validator safe_html \ + -parameter { + plugins + folder_id + script_dir + width + height + {wiki false} + } + + richtext instproc editor {args} { + # + # TODO: this should be made a slot setting + # + #my msg "setting editor for [my name], args=$args,[llength $args]" + if {[llength $args] == 0} {return [my set editor]} + set editor [lindex $args 0] + if {[my exists editor] && $editor eq [my set editor] && [my exists __initialized]} return + + set editor_class [self class]::$editor + if {$editor ne "" && ![my hasclass $editor_class]} { + if {![my isclass $editor_class]} { + set editors [list] + foreach c [::xowiki::formfield::richtext info subclass] { + if {![$c exists editor_mixin]} continue + lappend editors [namespace tail $c] + } + error [_ xowiki.error-form_constraint-unknown_editor \ + [list name [my name] editor [my editor] editors $editors]] + } + foreach m [my info mixin] {if {[$m exists editor_mixin]} {my mixin delete $m}} + my mixin add $editor_class + #my msg "MIXIN $editor: [my info precedence]" + my reset_parameter + my set __initialized 1 + } + my set editor $editor + } + + richtext instproc initialize {} { + my display_field false + next + if {![my exists editor]} {my set editor xinha} ;# set the default editor + if {![my exists __initialized]} { + # Mixin the editor based on the attribute 'editor' if necessary + # and call initialize again in this case... + my editor [my set editor] + my initialize + } + } + + richtext instproc render_richtext_as_div {} { + #my msg "[my get_attributes id style {CSSclass class}]" + ::html::div [my get_attributes id style {CSSclass class}] { + if {[my wiki]} { + [my object] set unresolved_references 0 + [my object] set __unresolved_references [list] + #::html::t -disableOutputEscaping [[my object] substitute_markup [list [my value] text/html]] + ::html::t -disableOutputEscaping [[my object] substitute_markup [my value]] + } else { + ::html::t -disableOutputEscaping [my value] + } + } + ::html::div + } + + richtext instproc check=safe_html {value} { + # don't check if the user has sufficient permissions on the package + if {[::xo::cc permission \ + -object_id [::xo::cc package_id] \ + -privilege swa \ + -party_id [::xo::cc user_id]]} { + set msg "" + } else { + set msg [ad_html_security_check $value] + } + if {$msg ne ""} { + my uplevel [list set errorMsg $msg] + return 0 + } + return 1 + } + richtext instproc pretty_value {v} { + # for richtext, perform minimal output escaping + if {[my wiki]} { + return [[my object] substitute_markup $v] + } else { + return [string map [list @ "@"] $v] + } + } + + ########################################################### + # + # ::xowiki::formfield::richtext::ckeditor + # + # mode: wysiwyg, source + # skin: kama, v2, office2003 + # extraPlugins: tcl-list, is converted to comma list for js + # + ########################################################### + Class richtext::ckeditor -superclass richtext -parameter { + {editor ckeditor} + {mode wysiwyg} + {skin kama} + {toolbar Full} + {CSSclass xowiki-ckeditor} + {uiColor ""} + {inplace false} + {CSSclass xowiki-ckeditor} + {customConfig "../ck_config.js"} + {callback "/* callback code */"} + {destroy_callback "/* callback code */"} + {extraPlugins ""} + {templatesFiles ""} + {templates ""} + {contentsCss /resources/xowiki/ck_contents.css} + {imageSelectorDialog /xowiki/ckeditor-images/} + } + richtext::ckeditor set editor_mixin 1 + richtext::ckeditor instproc initialize {} { + if {[my set inplace]} { + my append help_text " #xowiki.ckeip_help#" + } + next + my set widget_type richtext + # Mangle the id to make it compatible with jquery; most probably + # not optimal and just a temporary solution + regsub -all {[.:]} [my id] "" id + my id $id + } + + richtext::ckeditor instproc js_image_helper {} { + ::xo::Page requireJS { + function xowiki_image_callback(editor) { + $(editor.element.$.form).submit(function(e) { + calc_image_tags_to_wiki_image_links(this); + }); + editor.setData(calc_wiki_image_links_to_image_tags(editor.getData())); + } + + function calc_image_tags_to_wiki_image_links (form) { + var calc = function() { + var wiki_link = $(this).attr('alt'); + $(this).replaceWith('[['+wiki_link+']]'); + } + $(form).find('iframe').each(function() { + $(this).contents().find('img[type="wikilink"]').each(calc); + }); + + $(form).find('textarea.ckeip').each(function() { + var contents = $('
'+this.value+'
'); + contents.find('img[type="wikilink"]').each(calc); + this.value = contents.html(); + }); + return true; + } + + function calc_wiki_image_links_to_image_tags (data) { + var pathname = window.location.pathname; + pathname = pathname.substr(pathname.lastIndexOf("/")+1,pathname.length) + console.log('pathname' + pathname); + pathname = pathname.replace(/:/ig,"%3a"); + var regex_wikilink = new RegExp('(\\[\\[./image:)(.*?)(\\]\\])', 'g'); + data = data.replace(regex_wikilink,'./image:$2'); + console.log('data' + data); + return data + } + } + } + + richtext::ckeditor instproc pathNames {fileNames} { + set result [list] + foreach fn $fileNames { + if {[regexp {^[./]} $fn]} { + append result $fn + } else { + append result "/resources/xowiki/$fn" + } + } + return $result + } + + richtext::ckeditor instproc render_input {} { + set disabled [expr {[my exists disabled] && [my disabled] ne "false"}] + if {![my istype ::xowiki::formfield::richtext] || $disabled } { + my render_richtext_as_div + } else { + ::xo::Page requireJS "/resources/xowiki/jquery/jquery.min.js" + ::xo::Page requireJS "/resources/xowiki/ckeditor/ckeditor_source.js" + #::xo::Page requireJS "/resources/xowiki/ckeditor/ckeditor.js" + ::xo::Page requireJS "/resources/xowiki/ckeditor/adapters/jquery.js" + ::xo::Page requireJS "/resources/xowiki/jquery-ui-1.8.17.custom.min.js" + ::xo::Page requireCSS "/resources/xowiki/jquery-ui-1.8.17.custom.css" + + # In contrary to the doc, ckeditor names instances after the id, + # not the name. + set id [my id] + set name [my name] + set package_id [[my object] package_id] + #my extraPlugins {timestamp xowikiimage} + + if {[lsearch [my extraPlugins] xowikiimage] > -1} { + my js_image_helper + set ready_callback {xowiki_image_callback(e.editor);} + } else { + set ready_callback "/*none*/;" + } + + set options [subst { + toolbar : '[my toolbar]', + uiColor: '[my uiColor]', + language: '[lang::conn::language]', + skin: '[my skin]', + startupMode: '[my mode]', + parent_id: '[[my object] item_id]', + package_url: '[$package_id package_url]', + extraPlugins: '[join [my extraPlugins] ,]', + contentsCss: '[my contentsCss]', + imageSelectorDialog: '[my imageSelectorDialog]', + ready_callback: '$ready_callback', + customConfig: '[my customConfig]' + }] + if {[my templatesFiles] ne ""} { + append options " , templates_files: \['[join [my pathNames [my templatesFiles]] ',' ]' \]\n" + } + if {[my templates] ne ""} { + append options " , templates: '[my templates]'\n" + } + + #set parent [[[my object] package_id] get_page_from_item_or_revision_id [[my object] parent_id]];# ??? + + if {[my set inplace]} { + if {[my value] eq ""} {my value " "} + my render_richtext_as_div + if {[my inline]} { + set wrapper_class "" + } else { + set wrapper_class "form-item-wrapper" + my callback {$(this.element.$).closest('.form-widget').css('clear','both').css('display', 'block');} + my destroy_callback {$(this).closest('.form-widget').css('clear','none');} + } + set callback [my callback] + set destroy_callback [my destroy_callback] + + ::xo::Page requireJS "/resources/xowiki/ckeip.js" + ::xo::Page requireJS [subst -nocommands { + \$(document).ready(function() { + \$( '\#$id' ).ckeip(function() { $callback }, { + name: '$name', + ckeditor_config: { + $options, + destroy_callback: function() { $destroy_callback } + }, + wrapper_class: '$wrapper_class' + }); + }); + }] + } else { + set callback [my callback] + ::xo::Page requireJS [subst -nocommands { + \$(document).ready(function() { + \$( '#$id' ).ckeditor(function() { $callback }, { + $options + }); + CKEDITOR.instances['$id'].on('instanceReady',function(e) {$ready_callback}); + }); + }] + next + } + } + } + + ########################################################### + # + # ::xowiki::formfield::richtext::wym + # + ########################################################### + Class richtext::wym -superclass richtext -parameter { + {editor wym} + {CSSclass wymeditor} + width + height + {skin silver} + {plugins "hovertools resizable fullscreen"} + } + richtext::wym set editor_mixin 1 + richtext::wym instproc initialize {} { + next + my set widget_type richtext + } + richtext::wym instproc render_input {} { + set disabled [expr {[my exists disabled] && [my disabled] ne "false"}] + if {![my istype ::xowiki::formfield::richtext] || $disabled } { + my render_richtext_as_div + } else { + ::xo::Page requireCSS "/resources/xowiki/wymeditor/skins/default/screen.css" + ::xo::Page requireJS "/resources/xowiki/jquery/jquery.min.js" + ::xo::Page requireJS "/resources/xowiki/wymeditor/jquery.wymeditor.pack.js" + set postinit "" + foreach plugin {hovertools resizable fullscreen embed} { + if {[lsearch -exact [my plugins] $plugin] > -1} { + switch -- $plugin { + embed {} + resizable { + ::xo::Page requireJS "/resources/xowiki/jquery/jquery.ui.js" + ::xo::Page requireJS "/resources/xowiki/jquery/jquery.ui.resizable.js" + append postinit "wym.${plugin}();\n" + } + default {append postinit "wym.${plugin}();\n"} + } + ::xo::Page requireJS "/resources/xowiki/wymeditor/plugins/$plugin/jquery.wymeditor.$plugin.js" + } + } + regsub -all {[.:]} [my id] {\\\\&} JID + + # possible skins are per in the distribution: "default", "sliver", "minimal" and "twopanels" + set config [list "skin: '[my skin]'"] + + #my msg "wym, h [my exists height] || w [my exists width]" + if {[my exists height] || [my exists width]} { + set height_cmd "" + set width_cmd "" + if {[my exists height]} {set height_cmd "jQuery(wym._box).find(wym._options.iframeSelector).css('height','[my height]');"} + if {[my exists width]} {set width_cmd "wym_box.css('width', '[my width]');"} + set postInit [subst -nocommand -nobackslash { + postInit: function(wym) { + wym_box = jQuery(".wym_box"); + $height_cmd + $width_cmd + $postinit + }}] + lappend config $postInit + } + if {$config ne ""} { + set config \{[join $config ,]\} + } + ::xo::Page requireJS [subst -nocommand -nobackslash { + jQuery(function() { + jQuery("#$JID").wymeditor($config); + }); + }] + + next + } + } + + ########################################################### + # + # ::xowiki::formfield::richtext::xinha + # + ########################################################### + + Class richtext::xinha -superclass richtext -parameter { + javascript + {height} + {style} + {wiki_p true} + {inplace false} + {slim false} + {CSSclass xinha} + } + richtext::xinha set editor_mixin 1 + richtext::xinha instproc initialize {} { + next + my set widget_type richtext + if {![my exists plugins]} { + my plugins \ + [parameter::get -parameter "XowikiXinhaDefaultPlugins" \ + -default [::xo::parameter get_from_package_key \ + -package_key "acs-templating" -parameter "XinhaDefaultPlugins"]] + } + my set options [my get_attributes editor plugins width height folder_id script_dir javascript wiki_p] + # for the time being, we can't set the defaults via parameter, + # but only manually, since the editor is used as a mixin, the parameter + # would have precedence over the defaults of subclasses + if {![my exists slim]} {my set slim false} + if {![my exists style]} {my set style "width: 100%;"} + if {![my exists height]} {my set height 350px} + if {![my exists wiki_p]} {my set wiki_p 1} + if {![my exists inplace]} {my set inplace false} + if {[my set inplace]} { + ::xo::Page requireJS "/resources/xowiki/xinha-inplace.js" + if {![info exists ::__xinha_inplace_init_done]} { + template::add_body_handler -event onload -script "xinha.inplace.init();" + set ::__xinha_inplace_init_done 1 + } + } + if {[my set slim]} { + my lappend options javascript { + xinha_config.toolbar = [['popupeditor', 'formatblock', 'bold','italic','createlink','insertimage'], + ['separator','insertorderedlist','insertunorderedlist','outdent','indent'], + ['separator','killword','removeformat','htmlmode'] + ]; + } + } + } + + richtext::xinha instproc render_input {} { + set disabled [expr {[my exists disabled] && [my disabled] ne "false"}] + if {![my istype ::xowiki::formfield::richtext] || $disabled} { + my render_richtext_as_div + } else { + # we use for the time being the initialization of xinha based on + # the site master + set ::acs_blank_master(xinha) 1 + set quoted [list] + foreach e [my plugins] {lappend quoted '$e'} + set ::acs_blank_master(xinha.plugins) [join $quoted ", "] + + array set o [my set options] + set xinha_options "" + foreach e {width height folder_id fs_package_id file_types attach_parent_id wiki_p package_id} { + if {[info exists o($e)]} { + append xinha_options "xinha_config.$e = '$o($e)';\n" + } + } + append xinha_options "xinha_config.package_id = '[::xo::cc package_id]';\n" + if {[info exists o(javascript)]} { + append xinha_options $o(javascript) \n + } + set ::acs_blank_master(xinha.options) $xinha_options + lappend ::acs_blank_master__htmlareas [my id] + + if {[my set inplace]} { + ::html::div [my get_attributes id name {CSSclass class} disabled] { + set href \# + set onclick "xinha.inplace.openEditor('[my id]');return false;" + ::html::a -style "float: right;" -class edit-item-button -href $href -onclick $onclick { + ::html::t -disableOutputEscaping   + } + ::html::div -id "[my id]__CONTENT__" { + ::html::t -disableOutputEscaping [my value] + } + } + my set hiddenid [my id]__HIDDEN__ + my set type hidden + ::html::input [my get_attributes {hiddenid id} name type value] {} + } else { + #::html::div [my get_attributes id name cols rows style {CSSclass class} disabled] {} + next + } + } + } + + ########################################################### + # + # ::xowiki::formfield::enumeration + # + ########################################################### + + # abstract superclass for select and radio + Class enumeration -superclass FormField -parameter { + {options} + {category_tree} + } + enumeration set abstract 1 + enumeration instproc initialize {} { + if {[my exists category_tree]} { + my config_from_category_tree [my category_tree] + } + next + } + enumeration abstract instproc render_input {} + + enumeration instproc get_labels {values} { + if {[my multiple]} { + set labels [list] + foreach v $values {lappend labels [list [my get_entry_label $v] $v]} + return $labels + } else { + return [list [list [my get_entry_label $values] $values]] + } + } + + enumeration instproc pretty_value {v} { + if {[my exists category_label($v)]} { + return [my set category_label($v)] + } + if {[my exists multiple] && [my set multiple]} { + foreach o [my set options] { + foreach {label value} $o break + set labels($value) [my localize $label] + } + set values [list] + foreach i $v {lappend values $labels($i)} + return [join $values {, }] + } else { + foreach o [my set options] { + foreach {label value} $o break + if {$value eq $v} {return [my localize $label]} + } + } + } + enumeration instproc config_from_category_tree {tree_name} { + # Get the options of a select or radio from the specified + # category tree. + # + # We could config as well from the mapped category tree, + # and get required and multiple from there.... + # + # The usage of the label does not seem to be very useful. + # + #set tree_id [category_tree::get_id $tree_name [my locale]] + + set package_id [[my object] package_id] + set tree_ids [::xowiki::Category get_mapped_trees -object_id $package_id -locale [my locale] \ + -names $tree_name -output tree_id] + + # In case there are multiple trees with the same name, + # take the first one. + # + set tree_id [lindex $tree_ids 0] + + if {$tree_id eq ""} { + my msg "cannot lookup mapped category tree name '$tree_name'" + return + } + set subtree_id "" + set options [list] + + foreach category [::xowiki::Category get_category_infos \ + -subtree_id $subtree_id -tree_id $tree_id] { + foreach {category_id category_name deprecated_p level} $category break + set category_name [ad_quotehtml [lang::util::localize $category_name]] + my set category_label($category_id) $category_name + if { $level>1 } { + set category_name "[string repeat {.} [expr {2*$level-4}]]..$category_name" + } + lappend options [list $category_name $category_id] + } + my options $options + my set is_category_field 1 + # my msg label_could_be=$tree_name,existing=[my label] + # if {![my exists label]} { + # my label $tree_name + # } + } + + ########################################################### + # + # ::xowiki::formfield::radio + # + ########################################################### + + Class radio -superclass enumeration -parameter { + {horizontal false} + {forced_name} + } + radio instproc initialize {} { + my set widget_type text(radio) + next + } + radio instproc render_input {} { + set value [my value] + foreach o [my options] { + foreach {label rep} $o break + set atts [my get_attributes disabled {CSSclass class}] + if {[my exists forced_name]} {set name [my forced_name]} {set name [my name]} + lappend atts id [my id]:$rep name $name type radio value $rep + if {$value eq $rep} {lappend atts checked checked} + ::html::input $atts {} + html::t "$label " + if {![my horizontal]} {html::br} + } + } + + ########################################################### + # + # ::xowiki::formfield::checkbox + # + ########################################################### + + Class checkbox -superclass enumeration -parameter { + {horizontal false} + } + checkbox instproc initialize {} { + my set multiple true + my set widget_type text(checkbox) + next + } + + + checkbox instproc value_if_nothing_is_returned_from_form {default} { + # Here we have to distinguish between two cases to: + # - edit mode: somebody has removed a mark from a check button; + # this means: clear the field + # - view mode: the fields were deactivted (made insensitive); + # this means: keep the old value + + #my msg "[my name] disabled=[my exists disabled]" + if {[my exists disabled]} {return $default} else {return ""} + } + checkbox instproc render_input {} { + # identical to radio, except "checkbox" type and lsearch; + # maybe we can push this up to enumeration.... + set value [my value] + foreach o [my options] { + foreach {label rep} $o break + set atts [my get_attributes disabled {CSSclass class}] + lappend atts id [my id]:$rep name [my name] type checkbox value $rep + if {[lsearch -exact $value $rep] > -1} {lappend atts checked checked} + ::html::input $atts {} + html::t "$label " + if {![my horizontal]} {html::br} + } + } + + + ########################################################### + # + # ::xowiki::formfield::select + # + ########################################################### + + Class select -superclass enumeration -parameter { + {multiple "false"} + } + + select instproc initialize {} { + my set widget_type text(select) + next + if {![my exists options]} {my options [list]} + } + + select instproc get_spec {} { + set select_atts [my get_attributes id name disabled {CSSclass class}] + if {[my multiple]} {lappend atts multiple [my multiple]} + set options [my options] + if {![my required]} { + set options [linsert $options 0 [list "--" ""]] + } + + set spec_options [list] + foreach o $options { + foreach {label rep} $o break + set atts [my get_attributes disabled] + lappend atts value $rep + if {[lsearch [my value] $rep] > -1} { + lappend atts selected on + } + lappend spec_options [list "option" $atts [list [list "#text" $label]]] + #lappend spec_options [list "#text" "\n"] + } + return [list select $select_atts $spec_options] + } + + select instproc render_input {} { + util_createDom [list [my get_spec]] + } + + select instproc render_input_old {} { + set atts [my get_attributes id name disabled {CSSclass class}] + if {[my multiple]} {lappend atts multiple [my multiple]} + set options [my options] + if {![my required]} { + set options [linsert $options 0 [list "--" ""]] + } + ::html::select $atts { + foreach o $options { + foreach {label rep} $o break + set atts [my get_attributes disabled] + lappend atts value $rep + #my msg "lsearch {[my value]} $rep ==> [lsearch [my value] $rep]" + if {[lsearch [my value] $rep] > -1} { + lappend atts selected on + } + ::html::option $atts {::html::t $label} + ::html::t \n + }} + } + + + ########################################################### + # + # ::xowiki::formfield::candidate_box_select + # + ########################################################### + Class create candidate_box_select -superclass select -parameter { + {as_box false} + {dnd true} + } + candidate_box_select set abstract 1 + + candidate_box_select instproc render_input {} { + #my msg "mul=[my multiple]" + # makes only sense currently for multiple selects + if {[my multiple] && [my dnd]} { + if {([my exists disabled] && [my disabled])} { + html::t -disableOutputEscaping [my pretty_value [my value]] + } else { + + # utilities.js aggregates "yahoo, dom, event, connection, animation, dragdrop" + set ajaxhelper 0 + ::xowiki::Includelet require_YUI_JS -ajaxhelper $ajaxhelper "utilities/utilities.js" + ::xowiki::Includelet require_YUI_JS -ajaxhelper $ajaxhelper "selector/selector-min.js" + ::xo::Page requireJS "/resources/xowiki/yui-selection-area.js" + + set js "" + foreach o [my options] { + foreach {label rep} $o break + set js_label [::xowiki::Includelet js_encode $label] + set js_rep [::xowiki::Includelet js_encode $rep] + append js "YAHOO.xo_sel_area.DDApp.values\['$js_label'\] = '$js_rep';\n" + append js "YAHOO.xo_sel_area.DDApp.dict\['$js_rep'\] = '$js_label';\n" + } + + ::html::div -class workarea { + ::html::h3 { ::html::t "Selection"} + set values "" + foreach v [my value] { + append values $v \n + set __values($v) 1 + } + my CSSclass selection + my set cols 30 + set atts [my get_attributes id name disabled {CSSclass class}] + + # TODO what todo with DISABLED? + ::html::textarea [my get_attributes id name cols rows style {CSSclass class} disabled] { + ::html::t $values + } + } + ::html::div -class workarea { + ::html::h3 { ::html::t "Candidates"} + ::html::ul -id [my id]_candidates -class region { + #my msg [my options] + foreach o [my options] { + foreach {label rep} $o break + # Don't show current values under candidates + if {[info exists __values($rep)]} continue + ::html::li -class candidates {::html::t $rep} + } + } + } + ::html::div -class visual-clear { + ;# maybe some comment + } + ::html::script { html::t $js } + } + } else { + next + } + } + + ########################################################### + # + # ::xowiki::formfield::abstract_page + # + ########################################################### + + Class abstract_page -superclass candidate_box_select -parameter { + {as_box false} + {multiple_style comma} + } + abstract_page set abstract 1 + + abstract_page instproc initialize {} { + my set package_id [[my object] package_id] + #my compute_options + next + } + + abstract_page instproc fetch_entry_label {entry_label item_id} { + db_1row [my qn [self proc]] "select $entry_label from cr_items ci, cr_revisions cr + where cr.revision_id = ci.live_revision and ci.item_id = $item_id" + return [set $entry_label] + } + abstract_page instproc get_entry_label {value} { + set item_id [[my set package_id] lookup -parent_id [[my object] parent_id] -name $value] + if {$item_id} { + return [::xo::cc cache [list my fetch_entry_label [my entry_label] $item_id]] + } + return "" + } + + abstract_page instproc pretty_value {v} { + my instvar package_id + set object [my object] + set parent_id [$object parent_id] + my set options [my get_labels $v] + if {[my multiple]} { + foreach o [my set options] { + foreach {label value} $o break + set href [$package_id pretty_link -parent_id $parent_id $value] + set labels($value) "$label" + } + set hrefs [list] + foreach i $v { + if {![info exists labels($i)]} { + #my msg "can't determine label for value '$i' (values=$v, l=[array names labels])" + set labels($i) $i + } + set href [$package_id pretty_link -parent_id $parent_id $i] + lappend hrefs "$labels($i)" + } + if {[my multiple_style] eq "list"} { + return "\n" + } else { + return [join $hrefs {, }] + } + } else { + foreach o [my set options] { + foreach {label value} $o break + #my log "comparing '$value' with '$v'" + if {$value eq $v} { + if {[my as_box]} { + return [$object include [list $value -decoration rightbox]] + } + set href [$package_id pretty_link -parent_id $parent_id $value] + return "$label" + } + } + } + } + + abstract_page instproc render_input {} { + my compute_options + next + } + + ########################################################### + # + # ::xowiki::formfield::form_page + # + ########################################################### + Class form_page -superclass abstract_page -parameter { + {form} + {where} + {entry_label title} + } + + form_page instproc initialize {} { + my instvar form_object_item_ids package_id object + if {![my exists form]} { return } + next + set form_name [my form] + set package_id [$object package_id] + set form_objs [::xowiki::Weblog instantiate_forms \ + -parent_id [$object parent_id] \ + -default_lang [$object lang] \ + -forms $form_name -package_id $package_id] + + #set form_obj [[my object] resolve_included_page_name $form_name] + if {$form_objs eq ""} {error "Cannot lookup Form '$form_name'"} + + set form_object_item_ids [list] + foreach form_obj $form_objs {lappend form_object_item_ids [$form_obj item_id]} + } + form_page instproc compute_options {} { + my instvar form_object_item_ids where package_id + #my msg "[my name] compute_options [my exists form]" + if {![my exists form]} { + return + } + + array set wc {tcl true h "" vars "" sql ""} + if {[info exists where]} { + array set wc [::xowiki::FormPage filter_expression $where &&] + #my msg "where '$where' => wc=[array get wc]" + } + + set from_package_ids {} + set package_path [::$package_id package_path] + if {[llength $package_path] > 0} { + foreach p $package_path { + lappend from_package_ids [$p id] + } + } + set items [::xowiki::FormPage get_form_entries \ + -base_item_ids $form_object_item_ids \ + -form_fields [list] \ + -publish_status ready \ + -h_where [array get wc] \ + -package_id $package_id \ + -from_package_ids $from_package_ids] + + set options [list] + foreach i [$items children] { + # + # If the form_page has a different package_id, prepend the + # package_url to the name. TODO: We assume here, that the form_pages + # have no special parent_id. + # + set object_package_id [$i package_id] + if {$package_id != $object_package_id} { + set package_prefix /[$object_package_id package_url] + } else { + set package_prefix "" + } + + lappend options [list [$i title] $package_prefix[$i name]] + } + my options $options + } + + form_page instproc pretty_value {v} { + my options [my get_labels $v] + if {![my exists form_object_item_ids]} { + error "No forms specified for form_field '[my name]'" + } + my set package_id [[lindex [my set form_object_item_ids] 0] package_id] + next + } + + + ########################################################### + # + # ::xowiki::formfield::page + # + ########################################################### + Class page -superclass abstract_page -parameter { + {type ::xowiki::Page} + {with_subtypes false} + {glob} + {entry_label name} + } + + page instproc compute_options {} { + my instvar type with_subtypes glob + + set extra_where_clause "" + if {[my exists glob]} { + append extra_where_clause [::xowiki::Includelet glob_clause $glob] + } + + set package_id [[my object] package_id] + set options [list] + db_foreach [my qn instance_select] \ + [$type instance_select_query \ + -folder_id [$package_id folder_id] \ + -with_subtypes $with_subtypes \ + -select_attributes [list title] \ + -from_clause ", xowiki_page p" \ + -where_clause "p.page_id = bt.revision_id $extra_where_clause" \ + -orderby ci.name \ + ] { + lappend options [list [set [my entry_label]] $name] + } + my options $options + } + + page instproc pretty_value {v} { + my set package_id [[my object] package_id] + next + } + + + + ########################################################### + # + # ::xowiki::formfield::DD + # + ########################################################### + + Class DD -superclass select + DD instproc initialize {} { + my options { + {01 1} {02 2} {03 3} {04 4} {05 5} {06 6} {07 7} {08 8} {09 9} {10 10} + {11 11} {12 12} {13 13} {14 14} {15 15} {16 16} {17 17} {18 18} {19 19} {20 20} + {21 21} {22 22} {23 23} {24 24} {25 25} {26 26} {27 27} {28 28} {29 29} {30 30} + {31 31} + } + next + } + + ########################################################### + # + # ::xowiki::formfield::HH24 + # + ########################################################### + + Class HH24 -superclass select + HH24 instproc initialize {} { + my options { + {00 0} {01 1} {02 2} {03 3} {04 4} {05 5} {06 6} {07 7} {08 8} {09 9} + {10 10} {11 11} {12 12} {13 13} {14 14} {15 15} {16 16} {17 17} {18 18} {19 19} + {20 20} {21 21} {22 22} {23 23} + } + next + } + + ########################################################### + # + # ::xowiki::formfield::MI + # + ########################################################### + + Class MI -superclass select + MI instproc value args { + if {[llength $args] == 0} {return [my set value]} else { + set v [lindex $args 0] + if {$v eq ""} {return [my set value ""]} else { + # round to 5 minutes + my set value [lindex [my options] [expr {($v + 2) / 5}] 1] + } + } + } + MI instproc initialize {} { + my options { + {00 0} {05 5} {10 10} {15 15} {20 20} {25 25} + {30 30} {35 35} {40 40} {45 45} {50 50} {55 55} + } + next + } + + ########################################################### + # + # ::xowiki::formfield::MM + # + ########################################################### + + Class MM -superclass select + MM instproc initialize {} { + my options { + {01 1} {02 2} {03 3} {04 4} {05 5} {06 6} {07 7} {08 8} {09 9} {10 10} + {11 11} {12 12} + } + next + } + ########################################################### + # + # ::xowiki::formfield::mon + # + ########################################################### + + Class mon -superclass select + mon instproc initialize {} { + set values [lang::message::lookup [my locale] acs-lang.localization-abmon] + if {[lang::util::translator_mode_p]} {set values [::xo::localize $values]} + set last 0 + foreach m {1 2 3 4 5 6 7 8 9 10 11 12} { + lappend options [list [lindex $values $last] $m] + set last $m + } + my options $options + next + } + ########################################################### + # + # ::xowiki::formfield::month + # + ########################################################### + + Class month -superclass select + month instproc initialize {} { + set values [lang::message::lookup [my locale] acs-lang.localization-mon] + if {[lang::util::translator_mode_p]} {set values [::xo::localize $values]} + set last 0 + foreach m {1 2 3 4 5 6 7 8 9 10 11 12} { + lappend options [list [lindex $values $last] $m] + set last $m + } + my options $options + next + } + + ########################################################### + # + # ::xowiki::formfield::YYYY + # + ########################################################### + + Class YYYY -superclass numeric -parameter { + {size 4} + {maxlength 4} + } -extend_slot validator YYYY + + YYYY instproc check=YYYY {value} { + if {$value ne ""} { + return [expr {[catch {clock scan "$value-01-01 00:00:00"}] == 0}] + } + return 1 + } + + ########################################################### + # + # ::xowiki::formfield::youtube_url + # + ########################################################### + Class youtube_url -superclass text + youtube_url set urlre {^http://www.youtube.com/watch[?]v=([^?]+)([?]?)} + + youtube_url instproc initialize {} { + next + if {[my help_text] eq ""} {my help_text "#xowiki.formfield-youtube_url-help_text#"} + } + youtube_url instproc pretty_value {v} { + if {$v eq ""} { + return "" + } elseif {[regexp [[self class] set urlre] $v _ name]} { + return " + + + +\n" + } else { + return "'$v' does not look like a youtube url" + } + } + + ########################################################### + # + # ::xowiki::formfield::image_url + # + ########################################################### + + Class image_url -superclass text \ + -extend_slot validator image_check \ + -parameter { + href cssclass + {float left} width height + padding {padding-right 10px} padding-left padding-top padding-bottom + margin margin-left margin-right margin-top margin-bottom + border border-width position top botton left right + } + image_url instproc initialize {} { + next + if {[my help_text] eq ""} {my help_text "#xowiki.formfield-image_url-help_text#"} + } + image_url instproc entry_name {value} { + set value [string map [list %2e .] $value] + if {![regexp -nocase {/([^/]+)[.](gif|jpg|jpeg|png)} $value _ name ext]} { + return "" + } + return file:$name.$ext + } + image_url instproc check=image_check {value} { + if {$value eq ""} {return 1} + set entry_name [my entry_name $value] + if {$entry_name eq ""} { + my log "--img '$value' does not appear to be an image" + # no image? + return 0 + } + set folder_id [[my object] set parent_id] + if {[::xo::db::CrClass lookup -name $entry_name -parent_id $folder_id]} { + my log "--img entry named $entry_name exists already" + # file exists already + return 1 + } + if {[regexp {^file://(.*)$} $value _ path]} { + set f [open $path r] + fconfigure $f translation binary + set img [read $f] + close $f + } elseif {[catch { + set r [::xo::HttpRequest new -url $value -volatile] + set img [$r set data] + } errorMsg]} { + # cannot transfer image + my log "--img cannot tranfer image '$value' ($errorMsg)" + return 0 + } + #my msg "guess mime_type of $entry_name = [::xowiki::guesstype $entry_name]" + set import_file [ns_tmpnam] + ::xowiki::write_file $import_file $img + set file_object [::xowiki::File new -destroy_on_cleanup \ + -title $entry_name \ + -name $entry_name \ + -parent_id $folder_id \ + -mime_type [::xowiki::guesstype $entry_name] \ + -package_id [[my object] package_id] \ + -creation_user [::xo::cc user_id] \ + ] + $file_object set import_file $import_file + $file_object save_new + return 1 + } + image_url instproc pretty_value {v} { + set entry_name [my entry_name $v] + return [my pretty_image -parent_id [[my object] parent_id] $entry_name] + } + + + ########################################################### + # + # ::xowiki::formfield::include + # + ########################################################### + + # note that the includelet "include" can be used for implementing symbolic links + # to other xowiki pages. + Class include -superclass text -parameter { + } + + include instproc pretty_value {v} { + if {$v eq ""} { return $v } + + my instvar object + set item_id [$object get_property_from_link_page item_id] + if {$item_id == 0} { + # Here, we could call "::xowiki::Link render" to offer the user means + # to create the entry like with [[..]], if he has sufficent permissions...; + # when $(package_id) is 0, the referenced package could not be + # resolved + return "Cannot resolve symbolic link '$v'" + } + set link_type [$object get_property_from_link_page link_type] + $object lappend references [list $item_id $link_type] + + # + # resetting esp. the item-id is dangerous. Therefore we reset it immediately after the rendering + # + $item_id set_resolve_context \ + -package_id [$object package_id] -parent_id [$object parent_id] \ + -item_id [$object item_id] + set html [$item_id render] + #my msg "reset resolve-context" + $item_id reset_resolve_context + + return $html + } + + ########################################################### + # + # ::xowiki::formfield::redirect + # + ########################################################### + + Class redirect -superclass text + redirect instproc pretty_value {v} { + #ad_returnredirect -allow_complete_url $v + #ad_script_abort + return [[[my object] package_id] returnredirect $v] + } + + ########################################################### + # + # ::xowiki::formfield::CompoundField + # + ########################################################### + + Class CompoundField -superclass FormField -parameter { + {components ""} + {CSSclass compound-field} + } -extend_slot validator compound + + CompoundField instproc check=compound {value} { + #my msg "check compound in [my components]" + foreach c [my components] { + set error [$c validate [self]] + if {$error ne ""} { + set msg "[$c label]: $error" + my uplevel [list set errorMsg $msg] + #util_user_message -message "Error in compound field [$c name]: $error" + return 0 + } + } + return 1 + } + + CompoundField instproc set_disabled {disable} { + #my msg "[my name] set disabled $disable" + if {$disable} { + my set disabled true + } else { + my unset -nocomplain disabled + } + foreach c [my components] { + $c set_disabled $disable + } + } + + CompoundField instproc value {args} { + if {[llength $args] == 0} { + set v [my get_compound_value] + #my msg "[my name]: reading compound value => '$v'" + return $v + } else { + #my msg "[my name]: setting compound value => '[lindex $args 0]'" + my set_compound_value [lindex $args 0] + } + } + + CompoundField instproc set_compound_value {value} { + if {[catch {array set {} $value} errorMsg]} { + # this branch could be taken, when the field was retyped + ns_log notice "CompoundField: error during setting compound value with $value: $errorMsg" + } + # set the value parts for each components + foreach c [my components] { + # Set only those parts, for which attribute values pairs are + # given. Components might have their own default values, which + # we do not want to overwrite ... + if {[info exists ([$c name])]} { + $c value $([$c name]) + } + } + } + + CompoundField instproc get_compound_value {} { + # Set the internal representation based on the components values. + set value [list] + foreach c [my components] { + #my msg "lappending [$c name] [$c value] " + lappend value [$c name] [$c value] + } + #my msg "[my name]: get_compound_value returns value=$value" + return $value + } + + CompoundField instproc create_components {spec_list} { + # + # Build a component structure based on a list of specs + # of the form {name spec}. + # + my set structure $spec_list + my set components [list] + foreach entry $spec_list { + foreach {name spec} $entry break + # + # create for each component a form field + # + set c [::xowiki::formfield::FormField create [self]::$name \ + -name [my name].$name -id [my id].$name \ + -locale [my locale] -object [my object] \ + -spec $spec] + my set component_index([my name].$name) $c + my lappend components $c + } + } + + CompoundField instproc get_component {component_name} { + set key component_index([my name].$component_name) + if {[my exists $key]} { + return [my set $key] + } + error "no component named $component_name of compound field [my name]" + } + + CompoundField instproc exists_named_sub_component args { + # Iterate along the argument list to check components of a deeply + # nested structure. For example, + # + # my check_named_sub_component a b + # + # returns 0 or one depending whether there exists a component "a" + # with a subcomponent "b". + set component_name [my name] + set sub [self] + foreach e $args { + append component_name .$e + if {![$sub exists component_index($component_name)]} { + return 0 + } + set sub [$sub set component_index($component_name)] + } + return 1 + } + + CompoundField instproc get_named_sub_component args { + # Iterate along the argument list to get components of a deeply + # nested structure. For example, + # + # my get_named_sub_component a b + # + # returns the object of the subcomponent "b" of component "a" + set component_name [my name] + set sub [self] + foreach e $args { + append component_name .$e + #my msg "check $sub set component_index($component_name)" + set sub [$sub set component_index($component_name)] + } + return $sub + } + + CompoundField instproc get_named_sub_component_value {{-default ""} args} { + if {[eval my exists_named_sub_component $args]} { + return [[eval my get_named_sub_component $args] value] + } else { + return $default + } + } + + CompoundField instproc generate_fieldnames {{-prefix "v-"} n} { + set names [list] + for {set i 1} {$i <= $n} {incr i} {lappend names $prefix$i} + return $names + } + + CompoundField instproc render_input {} { + # + # Render content within in a fieldset, but with labels etc. + # + html::fieldset [my get_attributes id {CSSclass class}] { + foreach c [my components] { $c render } + } + } + + CompoundField instproc has_instance_variable {var value} { + set r [next] + if {$r} {return 1} + foreach c [my components] { + set r [$c has_instance_variable $var $value] + if {$r} {return 1} + } + return 0 + } + + CompoundField instproc convert_to_internal {} { + foreach c [my components] { + $c convert_to_internal + } + } + + CompoundField instproc get_spec {} { + set component_specs [list] + foreach c [my components] { + lappend component_specs [$c get_spec] + } + my set style "margin: 0px; padding: 0px;" + set atts [my get_attributes id style] + return [list "fieldset" $atts $component_specs] + } + + + ########################################################### + # + # ::xowiki::formfield::label + # + ########################################################### + + Class label -superclass FormField -parameter { + {disableOutputEscaping false} + } + label instproc render_item {} { + # sanity check; required and label do not fit well together + if {[my required]} {my required false} + next + } + label instproc render_input {} { + if {[my disableOutputEscaping]} { + ::html::t -disableOutputEscaping [my value] + } else { + ::html::t [my value] + } + # Include labels as hidden fields to avoid surprises when + # switching field types to labels. + my set type hidden + next + } + + + ########################################################### + # + # ::xowiki::formfield::child_pages + # + ########################################################### + Class child_pages -superclass label -parameter { + {form} + {publish_status all} + } + child_pages instproc initialize {} { + next + # + # for now, we allow just FormPages as child_pages + # + if {![my exists form]} { return } + my instvar object + my set form_objs [::xowiki::Weblog instantiate_forms \ + -parent_id [$object parent_id] \ + -default_lang [$object lang] \ + -forms [my form] \ + -package_id [$object package_id]] + } + child_pages instproc pretty_value {v} { + if {[my exists form_objs]} { + my instvar object + set count 0 + foreach form [my set form_objs] { + incr count [$form count_usages \ + -package_id [$object package_id] \ + -parent_id [$object item_id] \ + -publish_status [my publish_status]] + } + return $count + } else { + return 0-NULL + } + } + + ########################################################### + # + # ::xowiki::formfield::date + # + ########################################################### + + Class date -superclass CompoundField -parameter { + {format "DD MONTH YYYY"} + {display_format "%Y-%m-%d %T"} + } + # The default of a date might be all relative dates + # supported by clock scan. These include "now", "tomorrow", + # "yesterday", "next week", .... use _ for blanks + + date instproc initialize {} { + #my msg "DATE has value [my value]//d=[my default] format=[my format] disabled?[my exists disabled]" + my set widget_type date + my set format [string map [list _ " "] [my format]] + my array set defaults {year 2000 month 01 day 01 hour 00 min 00 sec 00} + my array set format_map { + SS {SS %S 1} + MI {MI %M 1} + HH24 {HH24 %H 1} + DD {DD %e 0} + MM {MM %m 1} + MON {mon %m 1} + MONTH {month %m 1} + YYYY {YYYY %Y 0} + } + #my msg "[my name] initialize date, format=[my format] components=[my components]" + foreach c [my components] {$c destroy} + my components [list] + + foreach element [split [my format]] { + if {![my exists format_map($element)]} { + # + # We add undefined formats as literal texts in the edit form + # + set name $element + set c [::xowiki::formfield::label create [self]::$name \ + -name [my name].$name -id [my id].$name \ + -locale [my locale] -object [my object] \ + -value $element] + $c set_disabled [my exists disabled] + if {[lsearch [my components] $c] == -1} {my lappend components $c} + continue + } + foreach {class code trim_zeros} [my set format_map($element)] break + # + # create for each component a form field + # + set name $class + set c [::xowiki::formfield::$class create [self]::$name \ + -name [my name].$name -id [my id].$name \ + -locale [my locale] -object [my object]] + #my msg "creating [my name].$name" + $c set_disabled [my exists disabled] + $c set code $code + $c set trim_zeros $trim_zeros + if {[lsearch [my components] $c] == -1} {my lappend components $c} + } + } + + date instproc set_compound_value {value} { + #my msg "[my name] original value '[my value]' // passed='$value' disa?[my exists disabled]" + # if {$value eq ""} {return} + if { $value eq {} } { + # We need to reset component values so that + # instances of this class can be used as flyweight + # objects. Otherwise, we get side-effects when + # we render the input widget. + foreach c [my components] { + $c value "" + } + return + } + set value [::xo::db::tcl_date $value tz] + #my msg "transformed value '$value'" + if {$value ne ""} { + set ticks [clock scan [string map [list _ " "] $value]] + } else { + set ticks "" + } + my set defaults(year) [clock format $ticks -format %Y] + my set defaults(month) [clock format $ticks -format %m] + my set defaults(day) [clock format $ticks -format %e] + my set defaults(hour) [clock format $ticks -format %H] + my set defaults(min) [clock format $ticks -format %M] + #my set defaults(sec) [clock format $ticks -format %S] + + # set the value parts for each components + foreach c [my components] { + if {[$c istype ::xowiki::formfield::label]} continue + if {$ticks ne ""} { + set value_part [clock format $ticks -format [$c set code]] + if {[$c set trim_zeros]} { + set value_part [string trimleft $value_part 0] + if {$value_part eq ""} {set value_part 0} + } + } else { + set value_part "" + } + #my msg "ticks=$ticks $c value $value_part" + $c value $value_part + } + } + + date instproc get_compound_value {} { + # Set the internal representation of the date based on the components values. + # Internally, the ansi date format is used. + set year ""; set month ""; set day ""; set hour ""; set min ""; set sec "" + if {[my isobject [self]::YYYY]} {set year [[self]::YYYY value]} + if {[my isobject [self]::month]} {set month [[self]::month value]} + if {[my isobject [self]::mon]} {set month [[self]::mon value]} + if {[my isobject [self]::MM]} {set month [[self]::MM value]} + if {[my isobject [self]::DD]} {set day [[self]::DD value]} + if {[my isobject [self]::HH24]} {set hour [[self]::HH24 value]} + if {[my isobject [self]::MI]} {set min [[self]::MI value]} + if {[my isobject [self]::SS]} {set sec [[self]::SS value]} + if {"$year$month$day$hour$min$sec" eq ""} { + return "" + } + # Validation happens after the value is retrieved. + # To avoid errors in "clock scan", fix the year if necessary + if {![string is integer $year]} {set year 0} + + foreach v [list year month day hour min sec] { + if {[set $v] eq ""} {set $v [my set defaults($v)]} + } + #my msg "$year-$month-$day ${hour}:${min}:${sec}" + if {[catch {set ticks [clock scan "$year-$month-$day ${hour}:${min}:${sec}"]}]} { + set ticks 0 ;# we assume that the validator flags these values + } + # TODO: TZ??? + #my msg "DATE [my name] get_compound_value returns [clock format $ticks -format {%Y-%m-%d %T}]" + return [clock format $ticks -format "%Y-%m-%d %T"] + } + + date instproc pretty_value {v} { + my instvar display_format + # + # Internally, we use the ansi date format. For displaying the date, + # use the specified display format and present the time localized. + # + # Drop of the value after the "." we assume to have a date in the local zone + regexp {^([^.]+)[.]} $v _ v + #return [clock format [clock scan $v] -format [string map [list _ " "] [my display_format]]] + if {$display_format eq "pretty-age"} { + return [::xowiki::utility pretty_age -timestamp [clock scan $v] -locale [my locale]] + } else { + return [lc_time_fmt $v [string map [list _ " "] [my display_format]] [my locale]] + } + } + + date instproc render_input {} { + # + # render the content inline withing a fieldset, without labels etc. + # + my set style "margin: 0px; padding: 0px;" + html::fieldset [my get_attributes id style] { + foreach c [my components] { $c render_input } + } + } + + + ########################################################### + # + # ::xowiki::boolean + # + ########################################################### + + Class boolean -superclass radio -parameter { + {default t} + } + boolean instproc value_if_nothing_is_returned_from_form {default} { + if {[my exists disabled]} {return $default} else {return f} + } + boolean instproc initialize {} { + # should be with cvs head message catalogs: + my options {{#acs-kernel.common_Yes# t} {#acs-kernel.common_No# f}} + #my options {{No f} {#acs-kernel.common_Yes# t}} + next + } + + ########################################################### + # + # ::xowiki::boolean_image + # + ########################################################### + + Class create boolean_image -superclass FormField -parameter { + {default t} + {t_img_url /resources/xowiki/examples/check_richtig.png} + {f_img_url /resources/xowiki/examples/check_falsch.png} + {CSSclass img_boolean} + } + boolean_image instproc initialize {} { + my type hidden + my set widget_type boolean(hidden) + } + boolean_image instproc render_input {} { + my instvar t_img_url f_img_url CSSclass + set title [expr {[my exists __render_help_text_as_title_attr] ? [my set help_text] : ""}] + ::html::img \ + -title $title \ + -class $CSSclass \ + -src [expr {[my value] ? $t_img_url : $f_img_url}] \ + -onclick "toggle_img_boolean(this,'$t_img_url','$f_img_url')" + ::html::input -type hidden -name [my name] -value [my value] + + ::xo::Page requireJS { + function toggle_img_boolean (element,t_img_url,f_img_url) { + var input = $(element).next(); + var state = input.val()== "t"; + if (state) { + input.val('f'); + $(element).attr('src',f_img_url); + } else { + input.val('t'); + $(element).attr('src',t_img_url); + } + } + } + } + + ########################################################### + # + # ::xowiki::formfield::scale + # + ########################################################### + + Class scale -superclass radio -parameter {{n 5} {horizontal true}} + scale instproc initialize {} { + my instvar n + set options [list] + for {set i 1} {$i <= $n} {incr i} { + lappend options [list $i $i] + } + my options $options + next + } + + + ########################################################### + # + # ::xowiki::formfield::form + # + ########################################################### + + Class form -superclass richtext -parameter { + {height 200} + } -extend_slot validator form + + form instproc check=form {value} { + set form $value + #my msg form=$form + dom parse -simple -html $form doc + $doc documentElement root + set rootNodeName "" + if {$root ne ""} {set rootNodeName [$root nodeName]} + return [expr {$rootNodeName eq "form"}] + } + + ########################################################### + # + # ::xowiki::formfield::form_constraints + # + ########################################################### + + Class form_constraints -superclass textarea -parameter { + {rows 5} + } -extend_slot validator form_constraints + # the form_constraints checker is defined already on the ::xowiki::Page level + + + ########################################################### + # + # ::xowiki::formfield::event + # + ########################################################### + + Class event -superclass CompoundField -parameter { + {multiday false} + } + + event instproc initialize {} { + #my msg "event initialize [my exists __initialized], multi=[my multiday] state=[my set __state]" + if {[my set __state] ne "after_specs"} return + my set widget_type event + if {[my multiday]} { + set dtend_format DD_MONTH_YYYY_#xowiki.event-hour_prefix#_HH24_MI + set dtend_display_format %Q_%X + } else { + set dtend_format HH24_MI + set dtend_display_format %X + } + my create_components [subst { + {summary {richtext,required,editor=wym,height=150px,label=#xowiki.event-title_of_event#}} + {dtstart {date,required,format=DD_MONTH_YYYY_#xowiki.event-hour_prefix#_HH24_MI, + default=now,label=#xowiki.event-start_of_event#,display_format=%Q_%X}} + {dtend date,format=$dtend_format,default=now,label=#xowiki.event-end_of_event#,display_format=$dtend_display_format} + {location text,label=#xowiki.event-location#} + }] + my set __initialized 1 + } + + event instproc get_compound_value {} { + if {![my exists __initialized]} { + return "" + } + set dtstart [my get_component dtstart] + set dtend [my get_component dtend] + if {![my multiday]} { + # If the event is not a multi-day-event, the end_day is not + # given by the dtend widget, but is taken from dtstart. + set end_day [lindex [$dtstart value] 0] + set end_time [lindex [$dtend value] 1] + $dtend value "$end_day $end_time" + #my msg "[$dtend name] set to '$end_day $end_time' ==> $dtend, [$dtend value]" + } + next + } + + event instproc pretty_value {v} { + array set {} [my value] + set dtstart [my get_component dtstart] + set dtstart_val [$dtstart value] + set dtstart_iso [::xo::ical clock_to_iso [clock scan $dtstart_val]] + + set dtend [my get_component dtend] + set dtend_val [$dtend value] + set dtend_txt "" + if {$dtend_val ne ""} { + set dtend_iso [::xo::ical clock_to_iso [clock scan $dtend_val]] + set dtend_txt " - [$dtend pretty_value $dtend_val]" + } + + set summary_txt "[[my get_component summary] value]" + set location [my get_component location] + set location_val [$location value] + set location_txt "" + if {$location_val ne ""} { + set location_label [$location label] + if {[regexp {^#(.+)#$} $location_label _ msg_key]} { + set location_label [lang::message::lookup [my locale] $msg_key] + } + set location_txt "$location_label: $location_val" + } + + append result \ + "
" \ + $summary_txt " " \ + "[$dtstart pretty_value $dtstart_val]" \ + $dtend_txt
\ + $location_txt \ + "
" + return $result + } + + + ########################################################### + # + # ::xowiki::formfield::repeatable + # + ########################################################### + + Class repeatable -superclass enumeration -parameter { + {min_elements 2} + {max_elements ""} + {repeat_type "text"} + } -extend_slot validator repeatable_num_of_elements + + repeatable instproc initialize {} { + my set type text + my set repeat_type "[namespace tail [my info class]]" ;# ::xowiki::formfield::date -> repeat_type=date + next + } + + repeatable instproc convert_to_internal {} { + set value [my value] + set new_value [list] + set isCompoundField [llength [my procsearch components]] + foreach v $value { + if { $v eq {} } { continue } + if { $isCompoundField} { + my value [list $v] + } else { + my value $v + } + next + # + # Whatever the effect of next, we still need + # to take it into account. + # + set new_v [[my object] get_property -name [my name]] + if { $new_v ne $value } { + lappend new_value $new_v + } + } + if { $new_value ne {} } { + [my object] set_property -new 1 [my name] $new_value + } + } + + repeatable instproc convert_to_external {value} { + set new_value [list] + foreach v $value { + lappend new_value [next $v] + } + return $new_value + } + + repeatable instproc set_compound_value {value} { + set c "" + array set values [list] + foreach v $value { + next $v + foreach c [my components] { + lappend values($c) [$c value] + } + } + if { $c ne {} } { + foreach c [my components] { + $c value $values($c) + } + } } + repeatable instproc get_compound_value {} { + # Iterate over all values so that inherited + # class methods would work, for instance, + # date, can only process one value at a time. + + set c "" + array set values [list] + foreach c [my components] { + set values($c) [$c value] + } + + set result [list] + if { $c ne {} } { + + # treat compound values one at a time + set count [llength $values($c)] + for {set i 0} {$i < $count} {incr i} { + foreach c [my components] { + $c value [lindex $values($c) $i] + } + lappend result [next] + } + + # restore values + foreach c [my components] { + $c value $values($c) + } + + } + return $result + } + + repeatable instproc validation_check {validator_method value} { + if { [string match {check=repeatable_*} $validator_method] } { + return [next] + } else { + foreach v $value { + if { ![my $validator_method $v] } { + return 0 + } + } + return 1 + } + } + + repeatable instproc check=repeatable_num_of_elements {value} { + my instvar min_elements max_elements + + set num_elements [llength [lsearch -not -all $value ""]] + ns_log notice "name=[my name] value= $value (ensure num_elements=$num_elements between $min_elements and [util_coalesce $max_elements +inf])" + ns_log notice "" + if { $num_elements < $min_elements } { + return 0 + } elseif { $max_elements ne {} && $num_elements > $max_elements } { + return 0 + } + } + + repeatable instproc render_input {} { + + if { ![my exists disabled] } { + my set disabled false + } + + if { ![my disabled] } { + ::xo::Page requireJS "/resources/xowiki/wu-repeatable.js" + } + + my instvar min_elements repeat_type + # sample data: my set value "a b c" + #my set value "a" + + # Note that we have a spec parameter that refers to + # the form definition, and a get_spec proc that refers + # to the specification for generating html and json. + set flyweight [::xowiki::formfield::$repeat_type new \ + -name [my name] \ + -locale [my locale] \ + -object [my object] \ + -proc get_spec {} { + lassign [next] tag atts children + lappend atts rep 1 + return [list $tag $atts $children] + }] + + + set rep 0 + foreach v [my value] { + incr rep + ::html::div { + ::html::div -class "wu-repeatable-arrows" { + ::html::a -class wu-repeatable-action -href "#" -onclick "return wu.repeatable.moveUp(this)" + + $flyweight id [my id]:$rep + $flyweight value $v + $flyweight render_input + if { ![my disabled] } { + ::html::a -href "#" -onclick "return wu.repeatable.delChoice(this)" { html::t "\[x\]" } + } + } + } + } + + for {set i $rep} {$i < $min_elements} {incr i} { + incr rep + ::html::div { + ::html::div -class "wu-repeatable-arrows" { + ::html::a -class wu-repeatable-action -href "#" -onclick "return wu.repeatable.moveUp(this)" + + $flyweight id [my id]:$rep + $flyweight value "" + $flyweight render_input + + if { ![my disabled] } { + ::html::a -href "#" -onclick "return wu.repeatable.delChoice(this)" { html::t "\[x\]" } + } + } + } + } + + if { ![my disabled] } { + $flyweight value "" + set spec [$flyweight get_json] + html::a -spec $spec -href "#" -onclick "return wu.repeatable.addChoice(this);" { html::t "add another" } + } + } + +} + +::xo::library source_dependent