xowiki::FormPage instproc www-edit (public)

 <instance of xowiki::FormPage[i]> www-edit \
    [ -validation_errors validation_errors ] \
    [ -disable_input_fields disable_input_fields ] [ -view on|off ]

Defined in /var/www/openacs.org/packages/xowiki/tcl/xowiki-www-procs.tcl

This web-callable method renders a form page in "edit" mode (i.e. provide input fields). The following query parameters can be used to influene the results "return_url", "title", "detail_link", "text", and "description".

Switches:
-validation_errors (optional)
-disable_input_fields (optional, defaults to "0")
-view (optional, boolean, defaults to "true")

Testcases:
create_workflow_with_instance
Source code:
#:log "edit [self args]"

:include_header_info -prefix form_edit
if {[::xo::cc mobile]} {
  :include_header_info -prefix mobile
}

set form [:get_form]
set anon_instances [:get_anon_instances]
#:log form=$form
#:log anon_instances=$anon_instances

set field_names [:field_names -form $form]
#:log field_names=$field_names
set form_fields [:create_form_fields $field_names]
#foreach f0 $form_fields {
#  ns_log notice "... created ff [$f0 name] [$f0 info class] '[$f0 value]'"
#}

if {$form eq ""} {
  #
  # Since we have no form, we create it on the fly
  # from the template variables and the form field specifications.
  #
  set form "<form></form>"
  set formgiven 0
} else {
  set formgiven 1
}
#:log formgiven=$formgiven

# check name field:
#  - if it is for anon instances, hide it,
#  - if it is required but hidden, show it anyway
#    (might happen, when e.g. set via @cr_fields ... hidden)
set name_field [:lookup_form_field -name _name $form_fields]

if {$anon_instances} {
  #$name_field config_from_spec hidden
} else {
  if {[$name_field istype ::xowiki::formfield::hidden]
      && [$name_field required] == true
    } {
    $name_field config_from_spec text,required
    $name_field type text
  }
}

#
# Include _text only, if explicitly needed (in form
# needed(_text))".
#
if {![dict exists ${:__field_needed} _text]} {
  #:msg "setting text hidden"
  set f [:lookup_form_field -name _text $form_fields]
  $f config_from_spec hidden
}

if {[:exists_form_parameter __disabled_fields]} {
  #
  # Disable some form-fields since these are disabled in the form
  # as well.
  #
  foreach name [:form_parameter __disabled_fields:0..n] {
    set f [:lookup_form_field -name $name $form_fields]
    $f set_disabled true
  }
}

#:show_fields $form_fields
#:log "__form_action [:form_parameter __form_action {}]"

if {[:form_parameter __form_action ""] eq "save-form-data"} {
  #
  # We want to save the form data, so we have to validate.
  #
  #:log "we have to validate"
  #
  # In case we are triggered internally, we might not have a
  # a connection. Therefore, do not validate the CSRF token.
  #
  if {![::${:package_id} exists __batch_mode]} {
    security::csrf::validate
  }

  lassign [:get_form_data $form_fields] validation_errors category_ids
  if {$validation_errors != 0} {
    #
    # We have validation errors.
    #
    #:log "$validation_errors validation errors in $form_fields"
    #foreach f $form_fields { :log "$f: [$f name] '[$f set value]' err: [$f error_msg] " }
    #
    # In case we are triggered internally, we might not have a
    # a connection, so we don't present the form with the
    # error messages again, but we return simply the validation
    # problems.
    #
    if {[::${:package_id} exists __batch_mode]} {
      set errors [list]
      foreach f $form_fields {
        if {[$f error_msg] ne ""} {
          lappend errors [list field [$f name] value [$f set value] error [$f error_msg]]
        }
      }
      set evaluation_errors ""
      if {[::${:package_id} exists __evaluation_error]} {
        set evaluation_errors "\nEvaluation error: [::${:package_id} set __evaluation_error]"
        ::${:package_id} unset __evaluation_error
      }
      error "[llength $errors] validation error(s): $errors $evaluation_errors"
    }
    #
    # Reset the name in error cases to the original one.
    #
    set :name [:form_parameter __object_name:signed,convert]
  } else {
    #
    # We have no validation errors, so we can save the content.
    #
    :save_data  -use_given_publish_date [expr {"_publish_date" in $field_names}]  [::xo::cc form_parameter __object_name:signed,convert ""$category_ids

    #
    # The data might have references. Perform the rendering here to compute
    # the references instead on every view (which would be safer, but slower). This is
    # roughly the counterpart to edit_data and save_data in ad_forms.
    #
    set content [:render -update_references all]
    #:log "after save refs=[expr {[info exists :references]?${:references} : {NONE}}]"

    set redirect_method [:form_parameter __form_redirect_method:wordchar "view"]
    #:log "redirect_method $redirect_method"

    if {$redirect_method eq "__none"} {
      return
    } else {
      if {$redirect_method ne "view"} {
        set qp "?m=$redirect_method"
      } else {
        set qp ""
      }
      set url [:pretty_link]$qp
      #
      # The method query_parameter uses now "::xo::cc set_parameter ...."
      # with highest precedence
      #
      set return_url [::${:package_id} query_parameter return_url:localurl $url]

      #:log "${:name}: url=$url, return_url=$return_url"
      ::${:package_id} returnredirect $return_url

      return
    }
  }
} elseif {[:form_parameter __form_action ""] eq "view-form-data"
          && ![info exists :__feedback_mode]
        } {
  #
  # We have nothing to save (maybe everything is read-only). Check
  # __feedback_mode to prevent recursive loops.
  #
  set redirect_method [:form_parameter __form_redirect_method:wordchar "view"]
  #:log "__redirect_method=$redirect_method"
  return [:www-view]
} else {
  #
  # Build the input form and display the current values.
  #
  #:log "form_action is something different: <[:form_parameter __form_action {}]>"
  if {[:is_new_entry ${:name}]} {
    set :creator [::xo::get_user_name [::xo::cc user_id]]
    set :nls_language [::${:package_id} default_locale]
  }

  #array set __ia ${:instance_attributes}
  :load_values_into_form_fields $form_fields

  foreach f $form_fields {
    set ff([$f name]) $f
  }

  #
  # For named entries, just set the entry fields to empty,
  # without changing the instance variables
  #
  #:log "my is_new_entry ${:name} = [:is_new_entry ${:name}]"
  if {[:is_new_entry ${:name}]} {

    if {$anon_instances} {
      set basename [::xowiki::autoname basename [${:page_template} name]]
      set name [::xowiki::autoname new -name $basename -parent_id ${:parent_id}]
      #:log "generated name=$name, page_template-name=[${:page_template} name]"
      $ff(_name) value $name
    } else {
      $ff(_name) value [$ff(_name) default]
    }
    if {![$ff(_title) istype ::xowiki::formfield::hidden]} {
      $ff(_title) value [$ff(_title) default]
    }
    foreach param [list title detail_link:localurl text description] {
      regexp {^([^:]+):?} $param . var
      if {[:exists_query_parameter $var]} {
        set value [:query_parameter $param]
        switch -- $var {
          detail_link {
            set f [:lookup_form_field -name $var $form_fields]
            $f value [$f convert_to_external $value]
          }
          title - text - description {
            set f [:lookup_form_field -name _$var $form_fields]
          }
        }
        $f value [$f convert_to_external $value]
      }
    }
  }

  $ff(_name) set transmit_field_always 1
  $ff(_nls_language) set transmit_field_always 1
}

#
# Some final sanity checks.
#
:form_fields_sanity_check $form_fields
:post_process_form_fields $form_fields

#
# "dom parse -html" has two problems with ADP tags like "<adp:icon ...>":
# a) If the tag name contains a colon or underscore, the tag is
#    treated like plain text, i.e. "<" and ">" are converted into
#    HTML entities.
# b) These tags have to be closed "<adp:icon ...>" is invalid.
#    Several existomg ADP tags have not closing tag.
#
# Therefore, we resolve the ADP tags before parsing the text by
# tdom. There should be some framework support to do this in
# general, but until we have this, resolve this problem here locally.
#
set form [::template::adp_parse_tags [:substitute_markup $form]]

#
# The following command would be correct, but does not work due to a bug in
# tdom.
# set form [:regsub_eval   #              [template::adp_variable_regexp] $form  #              {:form_field_as_html -mode edit "\\\1" "\2" $form_fields}]
# Due to this bug, we program around and replace the at-character
# by \x03 to avoid conflict with the input and we replace these
# magic chars finally with the fields resulting from tdom.

set form [string map [list @ \x03] $form]
#:msg form=$form

dom parse -html -- $form :doc
${:doc} documentElement :root

if {${:root} eq ""} {
  error "form '$form' is not valid"
}

::xo::require_html_procs
${:root} firstChild fcn
#:msg "orig fcn $fcn, root ${:root} [${:root} nodeType] [${:root} nodeName]"

set formNode [lindex [${:root} selectNodes //form] 0]
if {$formNode eq ""} {
  :msg "no form found in page [${:page_template} name]"
  ns_log notice "no form found in page [${:page_template} name]\n$form"
  set rootNode ${:root}
  $rootNode firstChild fcn
} else {
  set rootNode $formNode
  $rootNode firstChild fcn
  # Normally, the root node is the formNode, fcn is the first
  # child (often a TEXT_NODE), but ic can be even empty.
}


#
# Prepend some fields above the HTML contents of the form.
#
$rootNode insertBeforeFromScript {
  ::html::div {
    ::html::input -type hidden -name __object_name -value [::security::parameter::signed ${:name}]
    ::html::input -type hidden -name __form_action -value save-form-data
    ::html::input -type hidden -name __current_revision_id -value ${:revision_id}
    :extra_html_fields
    ::html::CSRFToken
  }
  #
  # Insert automatic form fields on top.
  #
  foreach att $field_names {
    #if {$formgiven && ![string match _* $att]} continue
    if {[dict exists ${:__field_in_form} $att]} continue
    set f [:lookup_form_field -name $att $form_fields]
    #:log "insert auto_field $att $f ([$f info class])"
    $f render_item
  }
} $fcn
#
# Append some fields after the HTML contents of the form.
#
set button_class(wym) ""
set button_class(xinha) ""
set has_file 0
$rootNode appendFromScript {
  # append category fields
  foreach f $form_fields {
    #:msg "[$f name]: is wym? [$f has_instance_variable editor wym]"
    if {[string match "__category_*" [$f name]]} {
      $f render_item
    } elseif {[$f has_instance_variable editor wym]} {
      set button_class(wym) "wymupdate"
    } elseif {[$f has_instance_variable editor xinha]} {
      set button_class(xinha) "xinhaupdate"
    }
    if {[$f has_instance_variable type file]} {
      set has_file 1
    }
  }

  #
  # Add a submit field(s) at bottom.
  #
  :render_form_action_buttons -CSSclass [string trim "$button_class(wym) $button_class(xinha)"]
}

if {$formNode ne ""} {

  if {[:exists_query_parameter "return_url"]} {
    set return_url [:query_parameter return_url:localurl]
  } else {
    #
    # When no return_url is specified and we edit a page different
    # from the invoked page, we use the calling page for default
    # redirection.  We do not want to redirect to some "embedded"
    # object after the edit. This happens if one edits e.g. a page
    # through a link.
    #
    if {[::xo::cc exists invoke_object]
        && [::xo::cc invoke_object] ne [self]
      } {
      #:log "=== no return_url specified, using [::xo::cc url] or [[::${:package_id} context] url]"
      set return_url [::xo::cc url]
      set return_url [ad_urlencode_url $return_url]
    }
  }
  set m [:form_parameter __form_redirect_method:wordchar "edit"]
  set url [export_vars -no_base_encode -base [:action_url] {m return_url}]
  #:log "=== setting action <$url> for form-action my-name ${:name}"
  $formNode setAttribute action $url method POST role form
  if {$has_file} {$formNode setAttribute enctype multipart/form-data}
  Form add_dom_attribute_value $formNode class [${:page_template} css_class_name]
}

:set_form_data $form_fields
if {$disable_input_fields} {
  #
  # (a) Disable explicit input fields.
  #
  foreach f $form_fields {$f set_disabled true}
  #
  # (b) Disable input in HTML-specified fields.
  #
  set disabled [Form dom_disable_input_fields $rootNode]
  #
  # Collect these variables in a hidden field to be able to
  # distinguish later between e.g. un unchecked checkmark and an
  # disabled field. Maybe, we have to add the fields from case (a)
  # as well.
  #
  $rootNode appendFromScript {
    ::html::input -type hidden -name "__disabled_fields" -value $disabled
  }
}
:post_process_dom_tree ${:doc} ${:root} $form_fields

set html [${:root} asHTML]
set html [:regsub_eval   {(^|[^\\])\x03([[:alnum:]_:]+)\x03} $html  {:form_field_as_html -mode edit "\\\1" "\2" $form_fields}]
#
# Replace unbalanced @ characters.
#
set html [string map [list \x03 @] $html]

#
# Handle unreported errors (in the future...). Unreported errors
# might occur, when a form-field was rendered above without
# "render_item". This can happen with inline rendering of the
# input fields where validation errors occur. Inline rendering
# happens very seldom (I know not a single occurrence in the
# wild). For such cases, one should define an extra field in the
# form with an idea, reparse the tree and insert the errors
# there. But first look, if we find a single occurrence.
#
set unprocessed {}
foreach f $form_fields {
  if {[$f set error_msg] ne ""
      && ![$f exists error_reported]
    } {
    ns_log notice "form-field [$f name] has unprocessed error msg '[$f set error_msg]'"
    #$f render_error_msg
    lappend unprocessed [$f name]
  }
}
#ns_log notice "=============== $unprocessed unprocessed error messages"
if {[llength $unprocessed] > 0} {
  ad_log warning "form has [llength $unprocessed] unprocessed "  "error messages in fields $unprocessed"
}

#:log "calling VIEW with HTML [string length $html]"
if {$view} {
  :www-view $html
} else {
  return $html
}
XQL Not present:
Generic, PostgreSQL, Oracle
[ hide source ] | [ make this the default ]
Show another procedure: