Index: openacs-4/packages/acs-tcl/tcl/acs-permissions-procs-oracle.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/acs-permissions-procs-oracle.xql,v diff -u -r1.7 -r1.8 --- openacs-4/packages/acs-tcl/tcl/acs-permissions-procs-oracle.xql 27 Mar 2002 19:13:09 -0000 1.7 +++ openacs-4/packages/acs-tcl/tcl/acs-permissions-procs-oracle.xql 5 Jul 2002 23:14:47 -0000 1.8 @@ -31,7 +31,7 @@ - select count(*) + select 1 from dual where 't' = acs_permission.permission_p(:object_id, :party_id, :privilege) Index: openacs-4/packages/acs-tcl/tcl/acs-permissions-procs-postgresql.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/acs-permissions-procs-postgresql.xql,v diff -u -r1.7 -r1.8 --- openacs-4/packages/acs-tcl/tcl/acs-permissions-procs-postgresql.xql 27 Mar 2002 19:13:09 -0000 1.7 +++ openacs-4/packages/acs-tcl/tcl/acs-permissions-procs-postgresql.xql 5 Jul 2002 23:14:47 -0000 1.8 @@ -24,8 +24,7 @@ - select count(*) - from dual + select 1 where 't' = acs_permission__permission_p(:object_id, :party_id, :privilege) Index: openacs-4/packages/acs-tcl/tcl/acs-permissions-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/acs-permissions-procs.tcl,v diff -u -r1.7 -r1.8 --- openacs-4/packages/acs-tcl/tcl/acs-permissions-procs.tcl 22 Jun 2002 17:07:22 -0000 1.7 +++ openacs-4/packages/acs-tcl/tcl/acs-permissions-procs.tcl 5 Jul 2002 23:14:47 -0000 1.8 @@ -41,7 +41,7 @@ set party_id [ad_conn user_id] } - return [db_string select_permission_p {}] + return [db_0or1row select_permission_p {}] } ad_proc -public require_permission { Index: openacs-4/packages/acs-tcl/tcl/form-processing-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/form-processing-procs.tcl,v diff -u -r1.6 -r1.7 --- openacs-4/packages/acs-tcl/tcl/form-processing-procs.tcl 31 May 2002 18:01:48 -0000 1.6 +++ openacs-4/packages/acs-tcl/tcl/form-processing-procs.tcl 5 Jul 2002 23:14:47 -0000 1.7 @@ -5,37 +5,295 @@ @author Don Baccus (dhogaza@pacifier.net) } -ad_proc -public ad_form_prototype { +ad_proc -public ad_form { args } { - I'll be adding more documentation as I get time (obviously) - I know error checking's incomplete, too ... + 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. - Three hidden values of interest are available to the caller of gp_form when processing - a submit: +

- 1. __new_p + We use the standard 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. +

+ + 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. + +

+ + Here's an example of a simple page implementing an add/edit form: + +

+
+    ad_page_contract {
+
+        Simple add/edit form
+
+    } {
+        my_table_key:optional
+    }
+
+    ad_form -name form_name -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"
+        }
+    } -new_data {
+        db_dml do_insert "
+            insert into my_table
+              (my_table_key, value)
+            values
+              (:key, :value)"
+        ad_returnredirect "somewhere"
+        return
+    } -edit_data {
+        db_dml do_update "
+            update my_table
+            set value = :value
+            where my_table_key = :key"
+        ad_returnredirect "somewhere"
+        return
+    }
+
+    gp_return_template
+
+    
+ +

+ + 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 gp_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. + +

+ + Here's a complete list of switches that are supported by ad_form: + +

+ +

+

-name

+

Declares the name of the form. Defaults to the name of the script being served.
+ +

-action

+

The name of the script to be called when the form is submitted. Defaults to the name of the script + being served. +
+ +

-html

+

The given html will be added to the "form" tag when page is rendered. This is commonly used to + define multipart file handling forms. +
+ +

-extend

+

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. +
+ +

-form

+

Declare form elements (described in detail below) +
+ +

-select_query

+

Defines a query that returns a single row containing values for each element of the form meant to be + modifiable by the user. +
+ +

-select_query_name

+

The name of a query to be looked up in the appropriate query file that returns a single row containing + values for each element of the form meant to be modifiable by the user. In the OpenACS 4 environment this + should normally be used rather than -select_query, as query files are the mechanism used to make the + support of multiple RDMBS systems possible. +
+ +

-edit_request

+

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. +
+ +

-confirm_template

+

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_submit

+

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. +
+ +

-new_data

+

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. +
+ +

-new_data

+

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 exisiting item if the data's stored in the + content repository. +
+
+ + 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. +
- 2. __confirmed_p +

__refreshing_p

+

+ This should be set true by Javascript widgets which change a form element then + submit the form to refresh values. +
+
- If a confirm_template name has been specified, it is returned to the user until - it sets _confirmed_p true. +

Declaring form elements

- 3. __refreshing_p + 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. - This should be set true by Javascript widgets which change a form element then - submit the form to refresh values. +

-} { + 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, type, 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. +

+ 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::set_property. + 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 conversion 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(sql_date),from_html(sql_date),optional
+    

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

+

+ +} { + set level [template::adp_level] # Are we extending the form? @@ -53,9 +311,8 @@ return -code error "No arguments to ad_form" } - set valid_args { form method action html name select_query select_query_name new_data \ - edit_data validate on_submit confirm_template \ - new_request edit_request }; + set valid_args { form method action html name select_query select_query_name new_data on_refresh + edit_data validate on_submit confirm_template new_request edit_request }; ad_arg_parser $valid_args $args @@ -103,6 +360,12 @@ return -code error "No \"form\" block has been specified for form \"$form_name\"" } + # If we're not extending + if { !$extend_p } { + global gp_conn + incr gp_conn(form_count) + } + #################### # # Step 1: Parse the form specification @@ -123,47 +386,50 @@ set element_names [list] array set af_element_parameters [list] - foreach element $form { - set element_name_part [lindex $element 0] + if { [info exists form] } { + foreach element $form { + set element_name_part [lindex $element 0] - # This can easily be generalized if we add more embeddable form commands ... + # This can easily be generalized if we add more embeddable form commands ... - if { [string equal $element_name_part "-section"] } { - lappend af_element_names($form_name) "[list "-section" [uplevel [list subst [lindex $element 1]]]]" - } else { - if { ![regexp {^([^ \t:]+)(?::([a-zA-Z0-9_,(|)]*))?$} $element_name_part match element_name flags] } { - return -code error "Form element '$element_name_part' doesn't have the right format. It must be var\[:flag\[,flag ...\]\]" - } + if { [string equal $element_name_part "-section"] } { + lappend af_element_names($form_name) "[list "-section" [uplevel [list subst [lindex $element 1]]]]" + } else { + if { ![regexp {^([^ \t:]+)(?::([a-zA-Z0-9_,(|)]*))?$} $element_name_part match element_name flags] } { + return -code error "Form element '$element_name_part' doesn't have the right format. It must be var\[:flag\[,flag ...\]\]" + } - lappend af_element_names($form_name) $element_name - set af_extra_args($element_name) [lrange $element 1 end] - set pre_flag_list [split [string tolower $flags] ,] - set af_flag_list(${form_name}__$element_name) [list] + lappend af_element_names($form_name) $element_name + set af_extra_args($element_name) [lrange $element 1 end] + set pre_flag_list [split [string tolower $flags] ,] + set af_flag_list(${form_name}__$element_name) [list] - # find parameterized flags. We only allow one parameter. - foreach flag $pre_flag_list { - set af_element_parameters($element_name:$flag) [list] - set left_paren [string first "(" $flag] - if { $left_paren != -1 } { - if { ![string equal [string index $flag end] ")"] } { - return -code error "Missing or misplaced end parenthesis for flag '$flag' on argument '$element_name'" + # find parameterized flags. We only allow one parameter. + foreach flag $pre_flag_list { + set af_element_parameters($element_name:$flag) [list] + set left_paren [string first "(" $flag] + if { $left_paren != -1 } { + if { ![string equal [string index $flag end] ")"] } { + return -code error "Missing or misplaced end parenthesis for flag '$flag' on argument '$element_name'" + } + set flag_stem [string range $flag 0 [expr $left_paren - 1]] + lappend af_element_parameters($element_name:$flag_stem) [string range $flag [expr $left_paren + 1] [expr [string length $flag]-2]] + lappend af_flag_list(${form_name}__$element_name) $flag_stem + } else { + lappend af_flag_list(${form_name}__$element_name) $flag } - set flag_stem [string range $flag 0 [expr $left_paren - 1]] - lappend af_element_parameters($element_name:$flag_stem) [string range $flag [expr $left_paren + 1] [expr [string length $flag]-2]] - lappend af_flag_list(${form_name}__$element_name) $flag_stem - } else { - lappend af_flag_list(${form_name}__$element_name) $flag } } + lappend element_names [lindex $af_element_names($form_name) end] } - lappend element_names [lindex $af_element_names($form_name) end] } # Check the validation block for boneheaded errors if it exists. We explicitly allow a form element # to appear twice in the validation block so the caller can pair different error messages to different # checks. We implement this by building a global list of validation elements global af_validate_elements + set af_validate_elements($form_name) [list] if { [info exists validate] } { foreach validate_element $validate { @@ -203,13 +469,13 @@ # if a confirm template has been specified, it will be returned unless __confirmed_p is set # true. This is most easily done by including resources/forms/confirm-button in the confirm # template. - + template::element create $form_name __confirmed_p -datatype integer -widget hidden -value 0 - + # javascript widgets can change a form value and submit the result in order to allow the # generating script to fill in a value such as an image. The widget must set __refreshing_p # true. - + template::element create $form_name __refreshing_p -datatype integer -widget hidden -value 0 } @@ -367,7 +633,7 @@ set key_name $af_key_name($form_name) upvar #$level $key_name $key_name - upvar #$level __gp_form_values__ values + upvar #$level __ad_form_values__ values # Check to see if we're editing an existing database value if { [info exists $key_name] } { @@ -479,6 +745,12 @@ } } + if { [template::form is_submission $form_name] && + [uplevel #$level {set __refreshing_p}] && + [info exists on_refresh] } { + ad_page_contract_eval uplevel #$level $on_refresh + } + if { [template::form is_valid $form_name] && ![uplevel #$level {set __refreshing_p}] } { # Run confirm and preview templates before we do final processing of the form @@ -510,18 +782,14 @@ # 1. an on_submit block (useful for forms that don't touch the database or can share smart Tcl API # for both add and edit forms) - # 2. an new_data block (when form_name:add_p is true) - # 3. an edit_data block (when form_name:add_p is false) + # 2. an new_data block (when __new_p is true) + # 3. an edit_data block (when __new_p is false) # We don't need to interrogate the af_parts structure because we know we're in the last call to # to ad_form at this point and that this call contained the "action blocks". - if { [info exists on_submit] } { - ad_page_contract_eval uplevel #$level $on_submit - } - # Execute our to_sql filters, if any, before passing control to the caller's - # new_data or edit_data blocks + # on_submit, new_data or edit_data blocks foreach element_name $af_element_names($form_name) { if { [llength $element_name] == 1 } { @@ -534,6 +802,10 @@ } } + if { [info exists on_submit] } { + ad_page_contract_eval uplevel #$level $on_submit + } + upvar #$level __new_p __new_p if { [info exists new_data] && $__new_p } { @@ -554,13 +826,13 @@ value } { Set the value of a particular element in the current form being built by - gp_form. + ad_form. @param element The name of the element @parma value The value to set } { - upvar #[template::adp_level] __gp_form_values__ values + upvar #[template::adp_level] __ad_form_values__ values set values($element) $value }