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 -r1.214 -r1.215 --- openacs-4/packages/xowiki/tcl/form-field-procs.tcl 31 Jul 2012 12:47:23 -0000 1.214 +++ openacs-4/packages/xowiki/tcl/form-field-procs.tcl 17 Aug 2012 08:00:21 -0000 1.215 @@ -100,6 +100,7 @@ return "" } + FormField instproc init {} { if {![my exists label]} {my label [string totitle [my name]]} if {![my exists id]} {my id [my name]} @@ -113,6 +114,36 @@ # application classes FormField instproc initialize {} {next} + FormField instproc get_dom_spec {} { + 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 @@ -138,7 +169,7 @@ if {$proc_info ne ""} { # we have a slot checker, call it #my msg "++ call-field level validator $validator_method '$value'" - set success [my $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 @@ -237,6 +268,7 @@ } FormField instproc behavior {mixin} { + # # Specify the behavior of a form field via # per object mixins @@ -260,6 +292,11 @@ } } + FormField instproc repeatable {} { + my mixin add ::xowiki::formfield::repeatable + my reset_parameter + } + FormField instproc interprete_single_spec {s} { if {$s eq ""} return @@ -271,6 +308,7 @@ 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} @@ -440,21 +478,9 @@ ::xo::Page requireJS "YAHOO.xo_form_field_validate.add('[my id]','$package_url');" } - 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] - } - } - ::html::input [eval my get_attributes type size maxlength id name value \ - pattern placeholder $pairs] {} - foreach att $booleanAtts { - if {[my exists __#$att]} {my unset __#$att} - } + #::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 @@ -1012,7 +1038,13 @@ 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 @@ -1856,6 +1888,7 @@ if {[my exists category_tree]} { my config_from_category_tree [my category_tree] } + next } enumeration abstract instproc render_input {} @@ -2019,7 +2052,33 @@ 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] @@ -2040,6 +2099,7 @@ }} } + ########################################################### # # ::xowiki::formfield::candidate_box_select @@ -2774,6 +2834,17 @@ } } + 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 @@ -2904,7 +2975,17 @@ 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 ""} {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 ""} { @@ -2993,6 +3074,7 @@ } } + ########################################################### # # ::xowiki::boolean @@ -3186,6 +3268,197 @@ "" 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" + + 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_dom_spec] + html::a -spec $spec -href "#" -onclick "return wu.repeatable.addChoice(this);" { html::t "add another" } + } + } + } ::xo::library source_dependent Index: openacs-4/packages/xowiki/tcl/xowiki-utility-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xowiki/tcl/xowiki-utility-procs.tcl,v diff -u -r1.19 -r1.20 --- openacs-4/packages/xowiki/tcl/xowiki-utility-procs.tcl 11 Aug 2011 12:32:58 -0000 1.19 +++ openacs-4/packages/xowiki/tcl/xowiki-utility-procs.tcl 17 Aug 2012 08:00:21 -0000 1.20 @@ -564,5 +564,100 @@ } } -::xo::library source_dependent + +proc util_jsquotevalue {value} { + return '[::xowiki::Includelet js_encode $value]' +} + + +proc util_map2json {pairs} { + set json_pairs [list] + foreach {key value} $pairs { + lappend json_pairs "'${key}':[util_jsquotevalue ${value}]" + } + return [join $json_pairs {,}] +} + +proc util_coalesce {args} { + foreach value $args { + if { $value ne {} } { + return $value + } + } +} + + +# +# intersect3 - perform the intersecting of two lists, returning a list +# containing three lists. The first list is everything in the first +# list that wasn't in the second, the second list contains the intersection +# of the two lists, the third list contains everything in the second list +# that wasn't in the first. +# + +proc util_intersect3 {list1 list2} { + set la1(0) {} ; unset la1(0) + set lai(0) {} ; unset lai(0) + set la2(0) {} ; unset la2(0) + foreach v $list1 { + set la1($v) {} + } + foreach v $list2 { + set la2($v) {} + } + foreach elem [concat $list1 $list2] { + if {[info exists la1($elem)] && [info exists la2($elem)]} { + unset la1($elem) + unset la2($elem) + set lai($elem) {} + } + } + list [lsort [array names la1]] [lsort [array names lai]] \ + [lsort [array names la2]] +} + + +proc util_createDom {list_of_specs} { + foreach spec $list_of_specs { + set cmdName [lindex $spec 0] + if { $cmdName eq "\#text" } { + lassign $spec cmdName text + html::t $text + } else { + lassign $spec cmdName atts inside_spec + html::${cmdName} $atts [list util_createDom $inside_spec] + } + } +} + + +proc util_spec2json {list_of_specs} { + + set result [list] + foreach spec $list_of_specs { + set cmdName [lindex $spec 0] + + lassign $spec cmdName atts inner_spec + set json "\{'tag':[util_jsquotevalue $cmdName]" + if { $atts ne {} } { + append json ",[util_map2json $atts]" + } + if { $inner_spec ne {} } { + lassign [lindex $inner_spec 0] nodeType text + if { ${nodeType} eq "\#text" } { + # text node + lassign [lindex $inner_spec 0] _nodeType_ text + append json ",'html':[util_jsquotevalue $text]" + } else { + # list of children nodes + append json ",'children':\[[util_spec2json $inner_spec]\]" + } + } + append json "\}" + lappend result $json + } + return [join $result {,}] +} + +::xo::library source_dependent \ No newline at end of file Index: openacs-4/packages/xowiki/tcl/xowiki-www-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xowiki/tcl/xowiki-www-procs.tcl,v diff -u -r1.306 -r1.307 --- openacs-4/packages/xowiki/tcl/xowiki-www-procs.tcl 22 Mar 2012 10:42:55 -0000 1.306 +++ openacs-4/packages/xowiki/tcl/xowiki-www-procs.tcl 17 Aug 2012 08:00:21 -0000 1.307 @@ -736,6 +736,7 @@ #my log "__redirect_method=$redirect_method" return [my view] } else { + # # display the current values # @@ -793,6 +794,7 @@ $ff(_nls_language) set transmit_field_always 1 } + # some final sanity checks my form_fields_sanity_check $form_fields my post_process_form_fields $form_fields @@ -1700,7 +1702,11 @@ } hidden - password - - text { $field setAttribute value $value} + text { + if { ![$field getAttribute rep "0"] } { + $field setAttribute value $value + } + } default {my log "can't handle $type so far $att=$value"} } } Index: openacs-4/packages/xowiki/www/resources/wu-repeatable.js =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xowiki/www/resources/wu-repeatable.js,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/xowiki/www/resources/wu-repeatable.js 17 Aug 2012 08:00:21 -0000 1.1 @@ -0,0 +1,136 @@ +// wu-base.js + +var wu = wu || {}; + +wu.log = function(o) { + if (window.console && o) { + window.console.log(o); + } +}; + +wu.setStyle = setStyle = function(el, property, val) { + el.style[property] = val; +}; + +wu.applyStyles = function(el, styles){ + if(styles){ + if(typeof styles == "string"){ + var re = /\s?([a-z\-]*)\:\s?([^;]*);?/gi; + var matches; + while ((matches = re.exec(styles)) != null){ + wu.setStyle(el,matches[1], matches[2]); + } + }else if (typeof styles == "object"){ + for (var style in styles){ + wu.setStyle(el,style, styles[style]); + } + }else if (typeof styles == "function"){ + wu.applyStyles(el, styles.call()); + } + } +} + +function isArray(v){ + return v && typeof v.length == 'number' && typeof v.splice == 'function'; +} +function createDom(o, parentNode){ + var el; + if (isArray(o)) { // Allow Arrays of siblings to be inserted + el = document.createDocumentFragment(); // in one shot using a DocumentFragment + for(var i = 0, l = o.length; i < l; i++) + { + createDom(o[i], el); + } + } else if (typeof o == "string") { // Allow a string as a child spec. + el = document.createTextNode(o); + } else { + el = document.createElement(o['tag']||'div'); + var useSet = !!el.setAttribute; // In IE some elements don't have setAttribute + for(var attr in o){ + if(attr == "tag" || attr == "children" || attr == "cn" || attr == "html" || attr == "style" || typeof o[attr] == "function") continue; + if(attr=="cls"){ + el.className = o["cls"]; + }else{ + if(useSet) el.setAttribute(attr, o[attr]); + else el[attr] = o[attr]; + } + } + wu.applyStyles(el, o['style']); + var cn = o['children'] || o['cn']; + if(cn){ + createDom(cn, el); + } else if(o['html']){ + el.innerHTML = o['html']; + } + } + if(parentNode){ + parentNode.appendChild(el); + } + return el; +}; + +// Mozilla 1.8 has support for indexOf, lastIndexOf, forEach, filter, map, some, every +// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:lastIndexOf +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (obj, fromIndex) { + if (fromIndex == null) { + fromIndex = 0; + } else if (fromIndex < 0) { + fromIndex = Math.max(0, this.length + fromIndex); + } + for (var i = fromIndex; i < this.length; i++) { + if (this[i] === obj) + return i; + } + return -1; + }; +} + +String.prototype.trim = function() { + return this.replace(/^\s+|\s+$/g,""); +} + +// wu-repeatable.js + +wu.repeatable = { + counter:0 +}; + +wu.repeatable.addChoice = function(e) { + + wu.log(e); + wu.repeatable.counter++; + + // TODO: get the spec attribute, generate name, and create dom + // render_input ensures that SPEC["text"] is included + // (via a call to ::xo::formfield::text->require_spec or some such) + // Example of spec attribute: + // {'tag':'input','cls':'wu-repeatable-choice','type':'text','name':'some_id:'}; + var json = e.getAttribute('spec'); + var spec = eval("(" + json + ')'); + + // use negative count for ids to avoid conflicts with + // code generated on the server-side + spec['id'] = spec['name'] + ':-' + wu.repeatable.counter; + + var d = document; + var el = e.parentNode; + var newNode=createDom({'tag':'div', + 'children':[{'tag':'div','cls':'wu-repeatable-arrows', + 'children':[{'tag':'a','cls':'wu-repeatable-action','href':'#','onclick':'return wu.repeatable.moveUp(this)','html':''}]}, + spec, + {'tag':'a','cls':'fl','href':'#','onclick':'return wu.repeatable.delChoice(this)','html':'[x]'} + ]}); + + el.insertBefore(newNode,e); + return false; +}; + +wu.repeatable.delChoice = function(e) { + var el = e.parentNode.parentNode; + el.removeChild(e.parentNode); + wu.repeatable.update(); + return false; +}; + +wu.repeatable.update = function() {} \ No newline at end of file