ad_form. Ad_form has nice error handing, and a very slick UI, including inline error reporting. However, it is very opaque sometimes, and after using some other form APIs on other platforms, it is confusing by comparison (writing a new form API for OpenACS would be a great contribution I think). My documentation here mostly deals with advanced issues with ad_form, or with common errors. Other documentation deals more with the basics.
ns_log notice it's my page!
set mypage [ns_getform]
if {$mypage eq ""} {
ns_log notice "no form was submitted on my page"
} else {
ns_log notice "the following form was submitted on my page"
ns_set print $mypage
}
- put {var ""} into ad_page_contract
- put {var {value $var}} into ad_form element
if { something or another }
set checked_p [list checked ""]
} else {
set checked_p ""
}
Then, in your ad_form declaration:
{
use_dependency_p:text(checkbox)
{label "Use dependency"}
{options {{"" "t"}}}
{html $checked_p}
}
However, the options section doesn't seem to work. If it is checked, the value will be "on"
{category_type:text(select)
{label "Type"}
{options {[db_list_of_lists get_category_types { }]} }
}
In the .xql file:
<fullquery name="get_category_types">
<querytext>
select
short_name,
category_id as my_cat_id
from
rl_resource_category_type
</querytext>
</fullquery>
First of all, at the top of your .tcl page:
ad_page_contract {
...
} {
{planned_end_date ""}
...
}
Then, in your ad_form declaration:
{planned_end_date:date(date),optional
{label "Planned end date"}
{help}
}
In your .xql file, when inserting:
In your .xql file, when selecting:<fullquery name="new_project_item">
<querytext>
select pm_project__new_project_item (
:project_id,
...
to_timestamp(:planned_end_date,'YYYY MM DD HH24 MI SS'),
...
);
</querytext>
</fullquery>
<fullquery name="project_query">
<querytext>
select
item_id,
...
to_char(planned_end_date,'YYYY MM DD') as planned_end_date,
...
FROM
pm_projectsx
where project_id = :project_id
</querytext>
</fullquery>
</queryset>
{goal:text(textarea)
{label "[set project_term] goal"}
{optional}
{value $goal}
{html { rows 5 cols 40 wrap soft}}}
number of Tasks. The previous page had a form which passed in the number of Tasks to create, but I wanted to have the ad_form let me create several tasks (number) on the same page.
Initially, I tried to just use ad_form -extend inside a for loop. However, you get an error like this:
Roberto Mello suggested having the names of each element be different, and building a string to feed to ad_form. This is the route I went. I did something like this after my initial declaration of an ad_form:Element 'task_title' already exists in form 'add_edit'.
for {set i 0} {$i <= $number} {incr i} {
append add_edit_definition "
{task_title_$i:text
{label \"Subject #$i\"}
{html {size 59}}
}"
}
ad_form -extend -name add_edit -form $add_edit_definition
An even better solution, proposed by DaveB: http://www.openacs.org/irc/log/2003-08-01#T18-23-45 or http://openacs.org/forums/message-view?message_id=52016
Before I did this, I would look at Jeff's posting , which describes using lists instead of the string I built above.
Note that some of these methods may be incompatible with dates.
After these are passed in, the ad_page_contract looks at them, and if the variable is specified as a date, then it handles it intelligently.
Unfortunately, this conflicts with the standard way of having multiple items with the same name in ad_form. So we have to resort to massive hacking, of the worst sort.
ad_page_contract {
...
end_date:array,optional
...
}
...
# The evilest hack of all time.
# -------------------------------------------------------
# This is a workaround the fact that using multiple dates
# with ad_form is extremely difficult. Dates are formatted
# like arrays, with values like end_date.day, end_date.month,
# end_date.year, and end_date.format . The problem is we want
# to have multiple end_dates. Using the multiple method, we
# then get entries like this: end_date.1.year, end_date.2.year,
# end_date.2.day, etc..
#
# What this loop does is go through the array, and rename the
# values into other variables. We then feed these variables into
# the SQL function that creates the new tasks. This works. I'm
# sure there must be a better way to do it, but my posting on
# the forums didn't result in any other suggestions.
if {[info exists end_date]} {
set searchToken [array startsearch end_date]
while {[array anymore end_date $searchToken]} {
set keyname [array nextelement end_date $searchToken]
set keyvalu $end_date($keyname)
# element_num is 1...n, element_type is year, format, day, month
regexp {(.*)\.(.*)} $keyname match element_num element_type
set end_date_[set element_type]($element_num) $keyvalu
}
for {set i 1} {$i <= $number} {incr i} {
# set up date variable names
set end_date_$i [list $end_date_year($i) $end_date_month($i) $end_date_day($i) {} {} {}]
}
}
...
ad_form -name add_edit -form {
task_id:key
...
} -new_data {
set task_revisions [list]
# number is the number of dates or items
for {set i 1} {$i <= $number} {incr i} {
lappend task_revisions [db_exec_plsql new_task_item { *SQL* }]
}
}
for {set i 0} {$i <= $number} {incr i} {
# reading this code, you may wonder why we put the .$i at the end.
# DaveB showed me this trick. It lets you make a multiple out of
# the items by stuffing them in an array. Long live DaveB.
ad_form -extend -name add_edit -form \
[list \
[list \
end_date.$i:date,to_sql(linear_date) \
{label "Deadline"} \
{format "MONTH DD YYYY"} \
{value {[util::date::today]}} \
{help} \
] \
]
}
Then, inside the .xql file:
<fullquery name="new_task_item">
<querytext>
select pm_task__new_task_item (
...
to_timestamp('[set end_date_$i]','YYYY MM DD HH24 MI SS'),
...
now(),
:user_id,
:peeraddr,
:package_id
);
</querytext>
</fullquery>
To take advantage of the ad_form built-in validation, remove validation rules from ad_page_contract, such as "integer,notnull". ad_page_contract validation returns a brief ugly error that instructs user to use the Back button and fix input. instead use the ad_form -validate block for complex validation, or nothing - this because ad_form by default makes all fields required (unless specified as optional). if a validation rule is not satisfied, ad_form will display an inline error message.
One can have multiple validation rules for the same input field within the ad_form -validate block.
one can write validation rules that check input against the database, such when it is necessary to make sure that a duplicate value is not being inserted into a column with a "unique" constraint.
See also: how to use validation blocks .
{myelement:text(text)
{section "A section header"}}
and thus group the form elements into different sections.
This is a simple summary of things I learned while working with ad_form for the first time. Although many of the things I learned should probably have been intuitive they were not for me so I decided to write a simple tutorial in case others have similar problems. Before you go though these examples it would be a good idea to pick up Roberto's ad_form quick reference sheet at:
http://www.brasileiro.net/writings/openacs/ad-form-quick-ref.pdf
Example 1: Simple Multiple select list
see /doc/form-builder.html from your openACS instance.
Example 2: Setting variables in the on_request section. This is really straight forward you just need to
set foo value
where foo is a widget defined in the -form section and value is the value that you would like to set it to. Note That you will not be able to retrieve other values that are set in this section. If you will need the value in another location it would be better to set it above the ad_form section
Example 3: Using -extend
This is really pretty well documented but just make sure that you define your form elements before you start the submit or other sections. This really becomes useful when you need to leave out part of the form or dynamically build the form.
#this is where we are placing the header information
ad_form -name accomplish -form {
{period_id:text(hidden) }
{work_id:text(hidden) }
{period_title:text(inform) {label "Progress report for"} }
{employee:text(inform) {label "Employee"} }
}
...some extra tcl code here
#This is the logic that will omit past data if it is not present
if {$prev_info_p == 1} {
ad_form -extend -name accomplish -form {
{emp_eval:text(inform) {section "Information from last year" } \
{value $prev_info(emp_eval)} \
{label "Action Plans/Training and Career Development Goals"}}
{sup_eval:text(inform) {value $prev_info(sup_eval)} \
{label "Supervisor Summary Comments"} }
{emp_response:text(inform) {value $prev_info(emp_response)} \
{label "Employee Comments/Responses (Optional)" } }
}
}
This will make is if there is no previous information for this employee this section of the form is omitted.
Example 4: Validating the data -validate
In this case the ad_form documentation does a great job in explaining this feature. For the lazy here is the code from the api-doc.
-validate { {value {[string length $value] >= 3} "\"value\" must be a string containing three or more characters" }
These statements can be as complex as you would like them to be. Here is a snippet from a form that has two submit buttons. If you save your work no password is required but to sign your work you need to include a password. This is the code that does that
{passwd
{ ( [string length $passwd] == 0 && $submit == "Save Work" ) \
|| [ad_check_password $user_id $passwd] }
"You Have entered an incorrect password" }
Speaking of two submit buttons on a form to get the second one I had to use the after_html segment as part of one of the widget's. The after_html and before_html are easy to use and do just what you think that they do.
I am sure there is a lot I have missed here but this should put you will on your way to using ad_form.
-Trenton Cameron