Quisp - building html forms and capturing user input
Quisp includes a good amount of functionality devoted to building data entry forms on the fly,
and capturing user input and doing something with it. First a discussion on capturing CGI user variables.
Capturing CGI user variables
This is the basic way to capture incoming CGI user variables, whether present in a page url or entered by users into a form.
The #cgivar directive is usually one of the first things done in a page.
It gets one or more CGI user variable varnames and loads into quisp variables of same name.
Each varname can have an optional datatype constraint code appended. Example:
#cgivar id(i*) mode(t) order(t) desc(s).
Codes are: i=integer n=numeric t=token s=safestring j=intlist l=tokenlist
An asterisk following the code means that the user var must be present.
During use, if a user variable arrives that does not conform to the datatype,
or a mandatory (*) var turns out to not be present, quisp will exit immediately with a terse error message.
For vars submitted from data entry forms it's best to use (r), (s) or as a last resort (z) and then code the validation
and give nicer error handling and message.
For non-interactive uses the #cgivar datatypes should be applied as strictly as possible as defense against
rogue user vars and cookies.
Explanation for some of the types:
t = token ... allows alphanumerics and limited other chars: _ - : .
s = safestring ... no errors are thrown but leading and trailing whitespace is removed, all <
are changed to [ , and all single and double quotes are removed.
r = rigorous-safestring ... no errors are thrown but leading and trailing whitespace is removed, all <
are changed to [ , and all occurances of these chars removed: " ' \ $ | ` ^ *
j = comma-delimited list of integers, no spaces
l = (lower case L) comma-delomited list of tokens, no spaces
z = anything. No processing is done. Same as no code at all, but using (z) makes it easy go grep out all uses in your codebase;
it must also be used if you want to use the asterisk.
Same as #cgivar above but gets an HTTP cookie instead of a CGI user variable.
To set a cookie print an HTTP set-cookie statement directly as the first line in your page, eg.
Set-Cookie: userprefs=wide; path=/;
(the other http headers will be juggled automatically for you by quisp).
Same as #cgivar above but used when capturing user form input from a <select ... multiple> item. Datatype will usually be (j) or (l).
Same as #cgivar above but used when capturing user form input from a <textarea> item.
#cgitextvar comments More about this below.
#sqlcgivar tablename [overwrite]
Does a #cgivar for every field in database tablename.
All vars are subjected to the (s) datatype rules.
Any var already in use will not be overwritten unless "overwrite" option is given
(this allows conversions and typechecking to be done beforehand where needed).
Same as #cgivar above but performs datatype checking on a variable that's already in use
(it does everything except loading CGI user variables).
#chkvar id(i*) mode(t)
Useful to capture responses from forms that have big series of checkboxes or <select>s .... discussed below
Basic approach for forms
You'll need a page for the html form.
There must also be a "form target" page to receive the user-entered values (using #cgivar and similar directives).
These are usually two pages but sometimes can be just one page submitting data to itself in a cyclical pattern.
For this discussion it's assumed that you're familiar with forms at the html level.
Also, remember that browsers often cache prior user form responses which can lead to confusion during development,
so when debugging be sure you're always getting a "fresh start".
On the page with the form we'll be building portions of the form dynamically.
We'll use the html <form> construct, with the "action" being your quisp executable.
To indicate the "form target" page use the #formtarget directive as shown.
To pass along any variables as html hidden variables use the #pass directive.
<form action="@CGIPROG" method=get>
..... html form content
Now for the html form content.
You pretty much just code
single-line <input> fields and checkboxes normally using plain html.
You might use a quisp variable as the "value" of an <input> field, and use #if/#else to display a checkbox
as already checked or not based on context. On the form target page you'll usually capture <input> result
using something like this: #cgivar userinput(s) and for checkboxes something like this:
#cgivar capschoice(t) or perhaps using #allresponses.
You'll code the "submit" button normally using plain html too.
For <select> option lists, radio buttons, and <textarea> fields the following directives help build them and
capture their input.
This generates the html code for a <select> option list form item. You include the <select> and </select> tags
and use the #optionlist and #options quisp directives to populate the list. Here's a simple example that works if all
choices are tokens. In this case if @maxrows has a value it will be presented as pre-selected, otherwise default is the first option.
"Selected" must always be specified to the left of "options".
<select name=maxrows size=1>
#optionlist selected=@maxrows options=20,50,100,200,500
Often however choices are not tokens, in which case the #options directive can be used like this:
<select name=mode size=1>
alpha alphabetical on last name
chrono chronological order
In the #options section there can be any number of lines and each line is an option
(first token will be the selected value, remaining content will be presented to the user).
The "Selected" value is always optional.
The "selected" value and each "option" value must always be a token with no embedded spaces or commas,
except when working with "select multiple" as discussed below, or if "allow_commas" option is used (below).
Use any #directives you like in the #options section;
a common technique is to use #sql to populate the option list as seen below.
Also, note that here the first option, which will be the fallback default, is crafted as a prompt.
<select name=state size=1>
error Select a state...
#sql select abbrev, fullname from us_states order by fullname
#while $sqlrow() = 0
#+ @abbrev @fullname
To display a blank option use "null" as the token value. A blank option can be an intuitive choice depending on context.
Handling "select multiple" Just add the "multiple" keyword to the <select> statement,
and perhaps increase the size to 4 or larger.
Then in the form target page use #cgilistvar instead of #cgivar and this will capture the result as a comma-delimited list of tokens.
With "multiple" you're also allowed to have a "selected" item that's a comma-delimited list of tokens, which causes
several options to be preselected.
Additional options that can be given as part of the #optionlist directive:
allow_commas .... allow commas to be part of the "selected" token
maxlen=50 .... truncate labels to a maximum length of 50 (or any number)
always_selected .... topmost option is displayed as selected
If your form has an array of <select> you might be able to use #allresponses to capture user choices, see below.
To generate radio buttons, use this example as a guide:
#setifnotgiven color = red
#radiobutton name=color value=red
click here to select red <br>
#radiobutton name=color value=blue
click here to select blue <br>
The #setifnotgiven causes "red" to be selected by default. If this were left out no button would be selected by default.
Textarea fields allow arbitrary-sized multi-line text chunks to be captured.
Quisp stores and retrieves this text in files we call "textchunks".
It's up to you as to how to build textchunk filenames for your application in order to keep everything organized.
It's often in your project directory (./textchunks) but it can be anywhere in the file system.
To present an empty textarea field use this:
<textarea name=comments rows=4 cols=40>
Suppose a user types something into the above textarea. On the formtarget page we'd capture it
and save it like this (assume there's some kind of id token being passed along):
#set filename = "comments/" @id
#savetext comments @filename
In this case the user input is saved in a file in your project directory, eg. if the id was 37853 then
it will be saved as ./textchunks/comments/37853 . The @filename can also be given as a full pathname if you prefer
for the files to be located elsewhere.
To display the text later on other pages use something like this:
#set filename = "comments/" @id
Similarly, if it's an edit form where user can make changes to the text, you can display the
text as pre-loaded in the <textarea> like this:
#set filename = "comments/" @id
<textarea name=comments height=4 width=40>
If you want to display the entered text on the formtarget page you don't need to do the file I/O since the text is
already present in your quisp environment, you can just do this, eg: #showtext comments
Some options that can be appended to #showtext include:
addbr .... add a <br> wherever a newline is found in the input
evalvars .... evaluate any quisp @variable constructs found in the text
Maximum number of characters that can be handled via #savetext and #showtext is 100,000 (MAXTEXTAREA)
#allresponses can be useful to handle forms that have big series of checkboxes or <select>s.
It's used in the form target page and it captures a set of user choices as a single comma-delimited list.
First, when you generate the form, give the checkbox <input items names that begin with
the same distinctive character or two, for example:
<input type=checkbox name="CB_red"> Red
<input type=checkbox name="CB_blue"> Blue
<input type=checkbox name="CB_green"> Green
Then on the form target page, use:
#allresponses colorchoices CB_*
This will load a quisp variable called colorchoices with a comma-delimited list of the names of the chosen checkboxes.
Don't use #cgivar.
For example if the user chose red and green and submitted the form, on the form target page @colorchoices would be
"CB_red,CB_green". You can then do #set colorlist = $change( "CB_", "", @colorlist )
to get rid of the CB_ prefixes before working further with @colorlist.
You can use #allresponses to capture input from an array of <select> items too.
In this case, specify a 3rd argument: a wildcard pattern to match values.
You'll then be building a list of all the <select> items where the user picked a certain choice, and you'd typically
have a separate #allresponses for each distinct choice of interest.