FOGE - A module to generate HTTP forms, store answers in databases,
generate the SQL tables and display statistical results.
Download the package from
http://youpou.lip6.fr/queinnec/Miscellaneous/FOGE/FOGE-latest.tar.gz FOGE is a project hosted by freshmeat.net: see
http://freshmeat.net/projects/foge/
FOGE is a module to conceive questions whose answer may be open
or a fixed choice of menu items. These questions are gathered in
sections and multiple sections may be displayed in a single HTML page.
When the form is filled, answers are stored in a database. The same
CGI script extracts statistical results or detailed information from
the database. The same CGI script may generate the SQL tables to
create in the database to hold the results.
I mainly use that package to get feedback from students on a course.
To have the answers in a database allows to perform some correlations
on individual questions. The main interest of the FOGE package is to
allow the form conceiver to maintain a single data structure for all
aspects: create table, generate the HTML form, handle the content of a
filled form, cull answers for statistical results.
Christian Queinnec <Christian.Queinnec@lip6.fr>
Here are some screenshots, they are only useful for the HTML
version. Their content is in French but the intention is just to show
the look generated by FOGE. The first screen shows a box to hold a
vote key followed by a section and questions with menus:
The next screen displays the statistics gathered on these questions:
Here are other types of questions and associated statistics
Finally, here is the end of the form:
But best of all is a
running example.
First, we define a sample.cgi file, the CGI script that will be used to
serve and cull forms. This file should be placed where it may be
served (for instance /var/www/cgi-bin/). This file will define the
questions to ask, their properties (open, single/multiple choice,
menu/buttons, etc.), the way they will appear, the way they may be
mapped onto SQL tables, the way they are statistically counted, etc.
Here follow a simple example.
You start with the usual incantations and the appropriate require form:
use CGI qw(:standard :html3);
use CGI::Carp qw(fatalsToBrowser);
require "FOGE.pl";
You may then define the lists that will serve as choices in buttons,
menus, etc. A number of choices are already defined in the FOGE
package.
my @Figures = ( 0 .. 9);
You define new questions (some are already defined in the FOGE
package) and you add them to the big bag of questions. You may define
a number of questions, you are not oblidge to use them all or in this
order.
FOGE::add_questions({
'figureAsTiny' => {
'label' => "Your preferred figure:",
'kind' => 'menu',
'sqltype' => 'smallint',
'choice' => \@Figures},
'figureAsText' => {
'label' => "Your preferred figure:",
'isValidRegexp' => '^\d$'}, # '$ just for fontlock!
'understood' => {
'label' => "Got it ?",
'kind' => 'menu',
'sqltype' => 'boolean',
'isValid' => 'coerceToSQLBool',
'choice' => \@FOGE::YesNo},
});
A question is identified by a key (such as figureAsTiny) in the big bag of
questions. The question is a hash taht must containt the text of the question
(under the 'label' key), the way it will appear (under the 'kind' key),
the SQL type (by default 'text') (under the optional key 'sqltype').
In case of a menu, a 'choice' should be specified; this is not mandatory
for the textfield or textarea kind that correspond to open questions.
Some facilities exist to check the answer with a regexp (under the
'isValidRegexp' key) or a function that converts the answer to some
specific SQL type (as encoded by Perl).
Now that we have a set of questions, we may build a section of some
questions. Note that a section may enforce a question as mandatory. A
section has a title, an associated SQL table and contains some
questions.
my @questions1 = (
{'id' => 'figureAsTiny'},
{'mandatory' => 1, 'id' => 'understood'});
my $section1 = {
'title' => "Your preferences",
'table' => 'preferred_figures',
'questions' => \@questions1,
};
We may build other sections. Once they are all created, we may finish
the complete form, saying:
my @theSections = (
$section1,
);
my $forms = {
'title' => "Important poll",
'enquiry' => "preferred-number", # The identifier for this poll
'introduction' => "This poll tries to figure out what is
the average preferred number.",
'ipMask' => '.*', # Accept any IP number.
'submit' => 'VOTE!',
'sets' => \@theSections,
# Time management
# Don't accept answers after that date:
'formEndTime' => timegm(0, 0, 23, 11, 2, 2002), # 11 mar 2002 23h
# Don't show statistics before that date:
'statStartTime' => timegm(0, 0, 23, 11, 2, 2002), # 11 mar 2002 23h
# SQL data base to be used:
'dbName' => 'dbi:Pg:dbname=someDB host=localhost',
'dbWriterName' => 'someDBuser',
'dbWriterPW' => 'aVerySecretePW',
'dbReaderName' => 'somePublicDBuser',
'dbReaderPW' => 'aPublicPW',
# URL password (Statistics may only be seen if knowing this password):
'URLpw' => 'anotherSecretePW',
};
Finally, you just call the default URL handler:
FOGE::default_main($forms);
And you are done! Now, you can direct your browser to the .cgi file
(pay attention that the FOGE.pl file must be accessible and readable),
You cannot however submit an answer since the database is not ready.
First, create the SQL tables with
perl ./sample.cgi 'action=genererTablesEnvoyer' > tables.sql
Then feed your SQL database (Postgresql (I did not try any other DB))
with this SQL table. Do not forget to create the user(s)
as specified
in the above $forms variable (these are the values of the 'dbReaderName'
to access statistical results and 'dbWriterName' to record filled-in
forms). Now you may sybmit an answer and it should be recorded in the
database. To see the statistical results, direct your browser towards:
http://.../sample.cgi/anotherSecretePW/see
and look.
At least three exist: there is a strong bias towards French as for
now. Not all features are currently described nor all desirable
features are implemented (radio-buttons instead of menus for
instance). Should generate the javascript to check mandatory fields
are filled in before submission.
Questions are represented by a hash and are added to the big bag of
questions via the FOGE:add_questions function. This function takes a
hash mapping question identifier towards hashes describing the
question. This hash should contain some mandatory fields:
- label
'label' is associated to a string to display the question.
- kind
'kind' specifies how to display the question in HTML. By
default, 'kind' is equal to 'menu' if there is a 'choice' entry in the
question otherwise it will be equal to 'text'. The possible kinds are:
- textfield
'textfield' corresponds to an open question whose answer is a
single word or line. Additionally the 'size' key specifies the length
of the textfield while the optional 'initial' key specifies the
initial content of this textfield.
- textarea
'textarea' corresponds to a textfield with several lines. The
'rows' key specifies the number of rows while 'columns' specify the
number of columns of the textarea. The optional 'initial' key
specifies the initial content of this textarea.
- menu
'menu' corresponds to a choice of one entry among a fix number
of predefined entries. The 'choice' key is mandatory and specifies the
list of possible answers. The optional 'initial' key specifies the
default choice.
- double_menu
'double_menu' is a specific question that corresponds to a
double answer. For instance ``What do you think of ... then'' and
``What do you think of ...(same thing)... now''. The 'choice' key is
mandatory and specifies the list of possible answers. The optional
'initial' key specifies the default choice.
- menu_twice
'menu_twice' is a question that expects a double answer. Each of these
answers is obtained via a different menu. The keys are suffixed with 1
or 2 depending on the target menu. Thus the 'choice1' key is mandatory
and specifies the list of possible answers for the first menu. The
optional 'initial1' key specifies the default choice. Keys 'choice2'
and 'initial2' concern the second menu.
- scroll
'scroll' is similar to 'menu' except that multiple answers are
allowed. The 'choice' key is mandatory and specifies the list of
possible answers. The optional 'initial' key specifies the default
choice.
- checkbox
'checkbox' is similar to 'scroll' except for the appearance. Instead
of a menu, the choice appears as a row of checkboxes. By default,
checkboxes support multiple selections. If only one is required, set
the 'multiple' option to 0 and the checkboxes will appear as
radio-buttons. The optional 'rows' and 'columns' key may customize the
appearance.
- scroll_plus_other
'scroll_plus_other' is similar to 'choice' except that an additional
'textarea' is provided for additional remarks. This is particularly
useful when the set of predefined choices might not cover the full
range of possible answers. The 'rows' key specifies the number of rows
of the textarea while 'columns' specify the number of columns of the
textarea. The 'choice' key is mandatory and specifies the list of
possible answers. The optional 'initial' key specifies the default
choice. Additionally the 'vertical' key if bound to true (i.e. 1)
displays the textarea under the scroll list. If multiples answers are
not expected, set 'multiple' to 0. Allowing multiple answers is
mentioned with the 'multipleAnswerPossible' text. The label of the
additional 'textarea' may be set with 'otherLabel'.
- sqltype
'sqltype' corresponds to the SQL type used to record answers. By
default, this is often 'text' which is the sensible default. In some
cases, it might be 'varchar(.)' (see code). If you have a choice with
numbers only, you may specify 'integer' as well.
- isValidRegexp
'isValidRegexp' corresponds to a regexp to check whether the
answer is valid. If the answer is not valid, the submission will not
be accepted, the data are not entered in the database and the user
receives a page telling him that he should correct the answer to the
question first.
- isValid
'isValid' corresponds to a user-defined predicate that should
check the answer. This predicate is invoked on the answer, its result
is considered as the appropriate answer to insert in the database. If
its result is the empty string, the answer is considered invalid and
the user is asked to reconsider his submission.
- sum
If 'sum' is true (equal to 1 for instance) then the accumulated sum
will be computed in addition to the observed frequencies. The
accumulated sum is computed from left to right. This is useful to
determine the median of a question with a lot of ordered choices.
For example, if the 'choices' array is (A, B, C), suppose the 20
answers to be (10, 3, 7) then the result of this question will look
like:
A B C
10/20 3/20 7/20
10/20 13/20 20/20
average
If 'average' is true (equal to 1 for instance) then the average is
computed and displayed after the array of observed frequency. For the
average to be computed, labels of choices should be numerically
interpretable! Labels with a number (int or float) within them get
this number as weight.
For example, if the 'choices' array is (<1h, <2h, >=2h), that is (less
than 1 hour, less than 2 hours, more than 2 hours) suppose the 20
answers to be (10, 3, 7) then the result of this question will look
like:
<1h <2h >=2h
10/20 3/20 7/20
10/20 13/20 20/20
Approximative average: 1.00
The average corresponds to (10*1 + 3*2 + 7*2)/20.
FUTURE work: other option may propose pie-charts!
When questions are defined in the big bag of questions, they may be
asked. Questions to be asked are gathered in a section. A section is
a hash with several possible keys. You may have more than one section
in a whole form. Here are the keys:
- title
'title' is the mandatory title (a string) of the section.
- prologue
'prologue' is an optional introduction (a string) to the section.
- epilogue
'epilogue' is an optional conclusion (a string) to the section.
- questions
'questions' is the mandatory ordered list of questions of that
section. The questions are specified in a list of hashes. Any of the
hashes may have the following keys:
- id
'id' specifies the question to use.
- mandatory
'mandatory' specifies whether the question is mandatory or
not. Observe that this is not a property of the question per se but an
external property on how the question is deployed. The associated
value is 0 or 1 (by default 0).
- secrete
'secrete' means that no statistics will be publicly displayed for that
question. Statistics are displayed when asked with a the URLpw (see
below) password. This is used when some questions are asked and only a
limited number of people may see the results.
- bof
'bof' (a French word meaning ``do not care''). If that
property is set then a --?-- item is added to the current
choice. Observe that this is not a property of the question per se but
an external property on how the question is deployed. The associated
value is 0 or 1 (by default 0).
- table
'table' is the name of the SQL table that will hold the answers
of the questions of that section.
When all sections are ready, you may define a form that is, a hash
with the following keys.
- title
'title' is the title of the whole form.
- introduction
'introduction' is the text (a string) heading the whole form (it
generally explains for whom is this form and what is the intention of
the form). This parameter is optional.
- conclusion
'conclusion' is the text (a string) suffixing the whole form (it
generally thank the user that fills the form). This parameter is optional.
- ipMask
'ipMask' is a regexp that filters the IP numbers that might
submit answers to this form. This parameter is optional.
- submit
'submit' is a word like Submit or VOTE! or Go that appears as
a button to submit an answer to the form.
- sets
'sets' specifies the list of sections. This parameter is mandatory.
- dbName
'dbName' specifies the database to use. It should follow DBI
conventions.
- dbReaderName
'dbReaderName' specifies the database user name to use when extracting
statistics. It should have SQL select access to the tables.
- dbReaderPW
'dbReaderPW' is the associated database password for the previous user.
- dbWriterName
'dbWriterName' specifies the database user name to use when inserting
new data. It should have SQL insert access to the tables.
- dbWriterPW
'dbWriterPW' is the associated database password for the previous user.
- dbAnswerTable
'dbAnswerTable' is the name of the table where all answers are
registered. By default, this table is named 'answer'. This is a very
simple table that records any answer, its originating IP number and
date. It confers to the answer a unique ID that is used in all other
tables to identify answers.
- URLpw
'URLpw' is a password that must appear on the URL to get statistics.
If left empty, anybody may look at the statistical results of the
form. The knowledge of the URLpw allows to see all statistics even the
ones that concern secrete questions and even before the statistics go
public.
- dbKeyTable
Sometimes, it is useful to prevent users to answer more than once to a
form and non-user to ever vote. In that case, specify a 'dbKeyTable'
entry that names a SQL table. This table should be filled with
keys. The form will be asked with an initial textfield asking for a
key. The table will be altered to record that the key is legal and had
been used. The table should have at least three columns (a key, the
name of an enquiry and an identifier (to match the answer in the
answer table)).
The textfield will preceded by the string specified by 'keyPrologue'
and followed by 'keyEpilogue'. The value sent by this textfield may be
set by 'keyName' (or 'dbKeyTable' if absent).
- year
'year' specifies the year of the records to consider when
computing statistics.
- ignoreLag
'ignoreLag' specifies a number of seconds. When two answers come
from the same IP number separated by less than this number of seconds,
the first one is ignored when computing statistics.
- pageFooter
'pageFooter' is the converse of 'introduction'. This is a text
(a string) that appears at the end of the form.
- css
'css' is the url of the CSS file to use.
- attentionColor
'attentionColor' is the color (by default red) used to display
text requiring some attention.
'tableAttributes', 'td1Attributes', 'td2Attributes', 'td3Attributes',
'beginTable', 'endTable' are html/css attributes used for the html
generation. They may be used locally to a section or globally.
This is the end of this terse documentation.