form-processing-procs.tcl

Form processing utilities.

Location:
packages/acs-tcl/tcl/form-processing-procs.tcl
Author:
Don Baccus <dhogaza@pacifier.net>

Procedures in this file

Detailed information

ad_form (public)

 ad_form [ -name name ] [ -extend ] [ -action action ] \
    [ -actions actions ] [ -mode mode ] [ -has_edit has_edit ] \
    [ -has_submit has_submit ] [ -method method ] [ -form form ] \
    [ -cancel_url cancel_url ] [ -cancel_label cancel_label ] \
    [ -html html ] [ -export export ] [ -select_query select_query ] \
    [ -select_query_name select_query_name ] \
    [ -show_required_p show_required_p ] [ -on_request on_request ] \
    [ -edit_request edit_request ] [ -new_request new_request ] \
    [ -confirm_template confirm_template ] [ -on_refresh on_refresh ] \
    [ -on_submit on_submit ] [ -new_data new_data ] \
    [ -edit_data edit_data ] [ -after_submit after_submit ] \
    [ -validate validate ] \
    [ -on_validation_error on_validation_error ] \
    [ -edit_buttons edit_buttons ] \
    [ -display_buttons display_buttons ] [ -fieldset fieldset ] \
    [ -csrf_protection_p csrf_protection_p ]

This procedure implements a high-level, declarative syntax for the generation and handling of HTML forms. It includes special syntax for the handling of forms tied to database entries, including the automatic generation and handling of primary keys generated from sequences. You can declare code blocks to be executed when the form is submitted, new data is to be added, or existing data modified. You can declare form validation blocks that are similar in spirit to those found in ad_page_contract.

Developer's Guide fo ad_form

We use the standard OpenACS Templating System (ATS) form builder's form and element create procedures to generate forms, and its state-tracking code to determine when to execute various code blocks. Because of this, you can use any form builder datatype or widget with this procedure, and extending its functionality is a simple matter of implementing new ones. Because ad_form is just a wrapper for the ATS, you must familiarize yourself with it to be able to use ad_form effectively.

In general, the full functionality of the form builder is exposed by ad_form, but with a much more user-friendly and readable syntax and with state management handled automatically.

Important note about how ad_form works: ad_form operates in two modes:
  1. Declaring the form
  2. Executing the form
Through the -extend switch, you can declare the form in multiple steps, adding elements. But as soon as you add an action block (on_submit, after_submit, new_data, edit_data, etc.), ad_form will consider the form complete, and execute the form, meaning validating element values, and executing the action blocks. The execution will happen automatically the first time you call ad_form with an action block, and after that point, you cannot -extend the form later. Also, if you don't supply any action blocks at all, the form will never be considered finished, and thus validation will not get executed. Instead, you will get an error when the form is rendered.

Bottom line:

  1. You must always have at least one action block, even if it's just -on_submit { }.
  2. You cannot extend the form after you've supplied any action block.

In order to make it possible to use ad_form to build common form snippets within procs, code blocks are executed at the current template parse level. This is necessary if validate and similar blocks are to have access to the form's contents but may cause surprises for the unwary. So be wary.

On the other hand when subst is called, for instance when setting values in the form, the caller's level is used. Why do this? A proc building a common form snippet may need to build a list of valid select elements or similarly compute values that need to be set in the form, and these should be computed locally.

Yes, this is a bit bizarre and not necessarily well thought out. The semantics were decided upon when I was writing a fairly complex package for Greenpeace, International and worked well there so for now, I'm leaving them the way they are.

Here's an example of a simple page implementing an add/edit form and exporting different kinds of values:


    ad_page_contract {


        Simple add/edit form

    } {
        {foo ""}
        my_table_key:optional
        many_values:multiple
        signed_var:verify
        big_array:array
    }

    ad_form -name form_name  -export {
        foo
        {bar none}
        many_values:multiple
        signed_var:sign
        big_array:array
      } -form {

        my_table_key:key(my_table_sequence)

        {value:text(textarea)
            {label "Enter text"}
            {html {rows 4 cols 50}}
        }

    } -select_query {
        select value from my_table where my_table_key = :my_table_key
    } -validate {
        {value
         {[string length $value] >= 3}
         "\$value\" must be a string containing three or more characters"
        }
    } -on_submit {

        foreach val $many_values {
          # do stuff
        }

        if {[info exists big_array(some_key)]} {
            set some_value $big_array(some_key)
        }

        set safe_verified_value $signed_var

    } -new_data {
        db_dml do_insert "
            insert into my_table
              (my_table_key, value)
            values
              (:key, :value)"
    } -edit_data {
        db_dml do_update "
            update my_table
            set value = :value
            where my_table_key = :key"
    } -after_submit {
        ad_returnredirect "somewhere"
        ad_script_abort
    }

    

In this example, ad_form will first check to see if "my_table_key" was passed to the script. If not, the database will be called to generate a new key value from "my_table_sequence" (the sequence name defaults to acs_object_id_seq). If defined, the query defined by "-select_query" will be used to fill the form elements with existing data (an error will be thrown if the query fails).

The call to ad_return_template then renders the page - it is your responsibility to render the form in your template by use of the ATS formtemplate tag.

On submission, the validation block checks that the user has entered at least three characters into the textarea (yes, this is a silly example). If the validation check fails the "value" element will be tagged with the error message, which will be displayed in the form when it is rendered. If the validation check returns true, one of the new_data or edit_data code blocks will be executed depending on whether or not "my_table_key" was defined during the initial request. "my_table_key" is passed as a hidden form variable and is signed and verified, reducing the opportunity for key spoofing by malicious outsiders.

This example includes dummy redirects to a script named "somewhere" to make clear the fact that after executing the new_data or edit_data block ad_form returns to the caller.

General information about parameters

Parameters which take a name (for instance "-name" or "-select_query_name") expect a simple name not surrounded by curly braces (in other words not a single-element list). All other parameters expect a single list to be passed in.

Two hidden values of interest are available to the caller of ad_form when processing a submit:

__new_p

If a database key has been declared, __new_p will be set true if the form submission is for a new value. If false, the key refers to an existing values. This is useful for forms that can easily process either operation in a single on_submit block, rather than use separate new_data and edit_data blocks.

__refreshing_p

This should be set true by JavaScript widgets which change a form element then submit the form to refresh values.

Declaring form elements

ad_form uses the form builder's form element create procedure to generate elements declared in the -form block. ad_form does rudimentary error checking to make sure the data type and widget exist, and that options are legal.

The -form block is a list of form elements, which themselves are lists consisting of one or two elements. The first member of each element sublist declares the form element name, datatype, widget, whether or not the element is a multiple element (multiselect, for instance), and optional conversion arguments. The second, optional member consists of a list of form element parameters and values. All parameters accepted by the form element create procedure are allowed.

  • Available datatypes. For example, the procedure template::data::validate::float on this list implements the 'float' datatype.
  • Available widgets. For example, the procedure template::widget::radio implements the 'radio' widget. Not all widgets are compatible with all datatypes.
  • Form element parameters and values. For example, the parameter -label "My label" is written {label "My label"} in the element sublist of the -form block to ad_form.

Some form builder datatypes build values that do not directly correspond to database types. When using the form builder directly these are converted by calls to datatype::get_property and datatype::acquire. When using ad_form, "to_html(property)", "to_sql(property)" and "from_sql(property)" declare the appropriate properties to be retrieved or set before calling code blocks that require the converted values. The "to_sql" operation is performed before any on_submit, new_data or edit_data block is executed. The "from_sql" operation is performed after a select_query or select_query_name query is executed. No automatic conversion is performed for edit_request blocks (which manually set form values). The "to_html" operation is performed before execution of a confirm template.

Currently only the date and currency datatypes require these conversion operations.

In the future the form builder will be enhanced so that ad_form can determine the proper conversion operation automatically, freeing the programmer from the need to specify them. When this is implemented the current notation will be retained for backwards compatibility.

ad_form defines a "key" pseudotype. Only one element of type "key" is allowed per form, and it is assigned the integer datatype. Only keys which are generated from a database sequence are managed automatically by ad_form. If the sequence name is not specified, the sequence acs_object_id_seq is used to generate new keys. Examples:

    my_key:key
    

Define the key "my_key", assigning new values by calling acs_object_id_seq.nextval

    my_key:key(some_sequence_name)
    

Define the key "my_key", assigning new values by calling some_sequence_name.nextval

    {my_key:text(multiselect),multiple       {label "select some values"}
                                              {options {first second third fourth fifth}}
                                              {html {size 4}}}

    

Define a multiple select element with five choices, in a four-line select box.

    {hide_me:text(hidden)                     {value 3}}
    

Define the hidden form element "hide_me" with the value 3

    start_date:date,to_sql(linear_date),to_html(sql_date),optional
    

Define the optional element "start_date" of type "date", get the sql_date property before executing any new_data, edit_data or on_submit block, set the sql_date property after performing any select_query.

    {email:text,nospell                      {label "Email Address"}
                                              {html {size 40}}}
    

Define an element of type text with spell-checking disabled. In case spell-checking is enabled globally for the widget of this element ("text" in the example), the "nospell" flag will override that parameter and disable spell-checking on this particular element. Currently, spell-checking can be enabled for these widgets: text, textarea, and richtext.

Switches:
-name (optional)
Declares the name of the form. Defaults to the name of the script being served.
-extend (optional, boolean)
Extend an existing form. This allows one to build forms incrementally. Forms are built at the template level. As a consequence one can write utility procs that use -extend to build form snippets common to several data entry forms. Note that the full form block must be built up (extended) and completed before any action blocks such as select_query, new_request, edit_request etc. are defined
-action (optional)
The name of the script to be called when the form is submitted. Defaults to the name of the script being served.
-actions (optional)
A list of lists of actions (e.g. {{" Delete " delete} {" Resolve " resolve}} ), which gets translated to buttons at the bottom of the form. You can find out what button was pressed with [template::form get_action form_id], usually in the -edit_request block to perform whatever actions you deem appropriate. When the form is loaded the action will be empty.
-mode (optional)
{ display | edit } If set to 'display', the form is shown in display-only mode, where the user cannot edit the fields. Each widget knows how to display its contents appropriately, e.g. a select widget will show the label, not the value. If set to 'edit', the form is displayed as normal, for editing. Switching to edit mode when a button is clicked in display mode is handled automatically
-has_edit (optional)
{ 0 | 1 } Set to 1 to suppress the Edit button automatically added by the form builder. Use this if you include your own.
-has_submit (optional)
{ 0 | 1 } Set to 1 to suppress the OK button automatically added by the form builder. Use this if you include your own.
-method (optional)
The standard METHOD attribute to specify in the HTML FORM tag at the beginning of the rendered form.
-form (optional)
Declare form elements (described in detail above)
-cancel_url (optional)
The URL the cancel button should take you to. If this is specified, a cancel button will show up during the edit phase.
-cancel_label (optional)
The label for the cancel button.
-html (optional)
The given html will be added to the "form" tag when page is rendered. This is commonly used to define multipart file handling forms.
-export (optional)
This options allows one to export data in current page environment to the page receiving the form. Variables are treated as "hidden" form elements which will be automatically generated. Each value is either a name, in which case the Tcl variable at the caller's level is passed to the form if it exists, or a name-value pair. The behavior of this option replicates that for vars argument in proc export_vars, which in turn follows specification for input page variables in ad_page_contract. In particular, flags :multiple, :sign and :array are allowed and their meaning is the same as in export_vars.
-select_query (optional)
Defines a query that returns a single row containing values for each element of the form meant to be modifiable by the user. Can only be used if an element of type key has been declared. Values returned from the query are available in the form, but not the ADP template (for that, use -edit_request instead).
-select_query_name (optional)
Identical to -select_query, except instead of specifying the query inline, specifies a query name. The query with that name from the appropriate XQL file will be used. Use -select_query_name rather than -select_query whenever possible, as query files are the mechanism used to make the support of multiple RDMBS systems possible.
-show_required_p (optional)
{ 0 | 1 } Should the form template show which elements are required. Use 1 or t for true, 0 or f for false.
-on_request (optional)
A code block which sets the values for each element of the form meant to be modifiable by the user when the built-in key management feature is being used or to define options for select lists etc. Set the values as local variables in the code block, and they'll get fetched and used as element values for you. This block is executed every time the form is loaded except when the form is being submitted (in which case the -on_submit block is executed.)
-edit_request (optional)
A code block which sets the values for each element of the form meant to be modifiable by the user. Use this when a single query to grab database values is insufficient. Any variables set in an -edit_request block are available to the ADP template as well as the form, while -select_query sets variables in the form only. Can only be used if an element of type key is defined. This block is only executed if the page is called with a valid key, i.e. a self-submit form to add or edit an item called to edit the data. Set the values as local variables in the code block, and they'll get fetched and used as element values for you.
-new_request (optional)
A code block which sets the values for each element of the form meant to be modifiable by the user. Use this when a single query to grab database values is insufficient. Can only be used if an element of type key is defined. This block complements the -edit_request block. You just need to set the values as local variables in the code block, and they'll get fetched and used as element values for you.
-confirm_template (optional)
The name of a confirmation template to be called before any on_submit, new_data or edit_data block. When the user confirms input control will be passed to the appropriate submission block. The confirmation template can be used to provide a bboard-like preview/confirm page. Your confirmation template should render the form contents in a user-friendly way then include "/packages/acs-templating/resources/forms/confirm-button". The "confirm-button" template not only provides a confirm button but includes the magic incantation that tells ad_form that the form has been confirmed by the user and that it is safe to call the proper submission block.
-on_refresh (optional)
Executed when the form comes back from being refreshed using JavaScript with the __refreshing_p flag set.
-on_submit (optional)
When the form is submitted, this code block will be executed before any new_data or edit_data code block. Use this if your form doesn't interact with the database or if the database type involved includes a Tcl API that works for both new and existing data. The values of the form's elements will be available as local variables. Calling 'break' inside this block causes the submission process to be aborted, and neither new_data, edit_data, nor after_submit will get executed. Useful in combination with template::form set_error to display an error on a form element.
-new_data (optional)
This code block will be executed when a form for a new database row is submitted. This block should insert the data into the database or create a new database object or content repository item containing the data. Calling 'break' inside this block causes the submission process to be aborted, and after_submit will not get executed. Useful in combination with template::form set_error to display an error on a form element.
-edit_data (optional)
This code block will be executed when a form for an existing database row is submitted. This block should update the database or create a new content revision for the existing item if the data's stored in the content repository. Calling 'break' inside this block causes the submission process to be aborted, and after_submit will not get executed. Useful in combination with template::form set_error to display an error on a form element.
-after_submit (optional)
This code block will be executed after the three blocks on_submit, new_data or edit_data have been executed. It is useful for putting in stuff like ad_returnredirect that is the same for new and edit.
-validate (optional)
A code block that validates the elements in the form. The elements are set as local values. The block has the following form:
{element_name
    {tcl code that returns 1 or 0}
    "Message to be shown by that element in case of error"
}
{...}
       
-on_validation_error (optional)
A code block that is executed if validation fails. This can be done to set a custom page title or some similar action.
-edit_buttons (optional)
-display_buttons (optional)
-fieldset (optional)
-csrf_protection_p (optional)
{ 0 | 1 } Should the form add automatically a hidden form field for csrf protection? Use 1 or t for true, 0 or f for false. Defaults to false.
See Also:

Partial Call Graph (max 5 caller/called nodes):
%3 test_template_widget_file template_widget_file (test acs-templating) ad_form ad_form test_template_widget_file->ad_form test_web_forums_message_and_reply web_forums_message_and_reply (test forums) test_web_forums_message_and_reply->ad_form ad_conn ad_conn (public) ad_form->ad_conn ad_page_contract_eval ad_page_contract_eval (public) ad_form->ad_page_contract_eval ad_return_complaint ad_return_complaint (public) ad_form->ad_return_complaint ad_return_error ad_return_error (public) ad_form->ad_return_error ad_return_exception_template ad_return_exception_template (public) ad_form->ad_return_exception_template Class ::Generic::Form Class ::Generic::Form (public) Class ::Generic::Form->ad_form Generic::Form instproc generate Generic::Form instproc generate (public) Generic::Form instproc generate->ad_form category::ad_form::add_widgets category::ad_form::add_widgets (public) category::ad_form::add_widgets->ad_form packages/acs-admin/lib/password-update.tcl packages/acs-admin/ lib/password-update.tcl packages/acs-admin/lib/password-update.tcl->ad_form packages/acs-admin/www/apm/parameter-delete.tcl packages/acs-admin/ www/apm/parameter-delete.tcl packages/acs-admin/www/apm/parameter-delete.tcl->ad_form

Testcases:
template_widget_file, web_forums_message_and_reply

ad_form_new_p (public)

 ad_form_new_p -key key

This is for pages built with ad_form that handle edit and add requests in one file. It returns 1 if the current form being built for the entry of new data, 0 if for the editing of existing data.

It does not make sense to use this in pages that don't use ad_form.

Switches:
-key (required)
the name of the key element. In the above example: ad_form_new_p -key item_id

Partial Call Graph (max 5 caller/called nodes):
%3 test_fs_create_folder fs_create_folder (test file-storage) ad_form_new_p ad_form_new_p test_fs_create_folder->ad_form_new_p test_fs_edit_folder fs_edit_folder (test file-storage) test_fs_edit_folder->ad_form_new_p packages/acs-subsite/www/admin/applications/application-add.tcl packages/acs-subsite/ www/admin/applications/application-add.tcl packages/acs-subsite/www/admin/applications/application-add.tcl->ad_form_new_p packages/calendar/www/cal-item-new.tcl packages/calendar/ www/cal-item-new.tcl packages/calendar/www/cal-item-new.tcl->ad_form_new_p packages/categories/lib/tree-form.tcl packages/categories/ lib/tree-form.tcl packages/categories/lib/tree-form.tcl->ad_form_new_p packages/categories/www/cadmin/tree-form.tcl packages/categories/ www/cadmin/tree-form.tcl packages/categories/www/cadmin/tree-form.tcl->ad_form_new_p packages/faq/www/admin/faq-add-edit.tcl packages/faq/ www/admin/faq-add-edit.tcl packages/faq/www/admin/faq-add-edit.tcl->ad_form_new_p

Testcases:
fs_create_folder, fs_edit_folder

ad_set_element_value (private)

 ad_set_element_value -element element value

Set the value of a particular element in the current form being built by ad_form.

Switches:
-element (required)
The name of the element
Parameters:
value (required)
The value to set

Partial Call Graph (max 5 caller/called nodes):
%3 ad_set_form_values ad_set_form_values (public) ad_set_element_value ad_set_element_value ad_set_form_values->ad_set_element_value template::adp_level template::adp_level (public) ad_set_element_value->template::adp_level

Testcases:
No testcase defined.

ad_set_form_values (public)

 ad_set_form_values [ args... ]

Set multiple values in the current form.

Partial Call Graph (max 5 caller/called nodes):
%3 ad_set_element_value ad_set_element_value (private) ad_set_form_values ad_set_form_values ad_set_form_values->ad_set_element_value

Testcases:
No testcase defined.
[ show source ]