|
QWizard - Display a series of questions, get the answers, and act on the answers. |
QWizard - Display a series of questions, get the answers, and act on the answers.
# # The following code works as a application *or* as a CGI script both: #
use QWizard;
my %primaries =
(
starting_node =>
{ title => "starting here",
introduction => "foo bar",
questions =>
[{ type => 'text',
name => 'mytext',
text => 'enter something:',
default => "hello world" },
{ type => 'checkbox',
text => 'yes or no:',
values => ['yes','no'],
name => 'mycheck'} ],
actions =>
[sub { return [
"msg: text = " . qwparam('mytext'),
"msg: checkbox = " . qwparam('mycheck')
];}]
}
);
my $qw = new QWizard(primaries => \%primaries,
title => "window title");
$qw->magic('starting_node');
# # PLEASE see the examples in the examples directory. #
QWizard displays a list of grouped questions, and retrieves and processes user-specified answers to the questions. Multiple question/answer sets may be displayed before the answers are dealt with. Once a ``commit'' action is taken (instigated by the user), a series of actions is performed to handle the answers. The actions are executed in the order required by the QWizard programmer.
QWizard's real power lies in its inherent ability to keep track of all state information between one wizard screen and the next, even in normally stateless transaction environments like HTTP and HTML. This allows a QWizard programmer to collect a large body of data with a number of simple displays. After all the data has been gathered and verified, then it can be handled as appropriate (e.g., written to a database, used for system configuration, or used to generate a graph.)
Current user interfaces that exist are HTML, Gtk2, Tk, and (minimally) ReadLine. A single QWizard script implementation can make use of any of the output formats without code modification. Thus it is extremely easy to write portable wizard scripts that can be used without modification by both graphical window environments (Gtk2 and Tk) and HTML-based web environments (e.g., CGI scripts.), as well with intercative command line enviornments (ReadLine).
Back-end interfaces (child classes of the QWizard::Generator module) are responsible for displaying the information to the user. Currently HTML, Gtk2, Tk and ReadLine, are the output mechanisms that work the best (in that order). Some others are planned (namely a curses version), but are not far along in development. Developing new generator back-ends is fairly simple and doesn't take a lot of code (assuming the graphic interface is fairly powerful and contains a widget library.)
QWizard operates by displaying a series of ``screens'' to the user. Each screen is defined in a QWizard construct called a primary that describes the attributes of a given screen, including the list of questions to be presented to the user. Primaries can contain questions, things to do immediately after the questions are answered (post_answers), and things to do once the entire series of screens have been answered (actions). Other information, such as a title and an introduction, can also be attached to a primary.
An example very minimal primary definition containing one question:
my %primaries = (
myprimary =>
{
title => "my screen title",
introduction => "optional introduction to the screen",
questions =>
[
{
type => 'checkbox',
text => 'Should the chicken cross the road?',
}
],
}
After defining a set of primaries, a new QWizard object must be created. The
QWizard new() constructor is given a set of options, such as window title
and a reference to a hash table containing the primaries. (The complete set
of options may be found in the ``QWizard new() Options'' section.) The question
display and data collection is started by calling the magic() routine of
the new QWizard object.
my $qw = new QWizard(primaries => \%primaries,
title => 'my title');
$qw->magic('myprimary');
There are examples distributed with the QWizard module sources that may help to understand the whole system and what it is capable of. See the examples directory of the QWizard source code tree for details. Also, QWizard was written mostly due to requirements of the Net-Policy project. Net-Policy makes very extensive use of QWizard and is another good place to look for examples. In fact, the QWizard CVS code is located inside the Net-Policy CVS tree. See http://net-policy.sourceforge.net/ for details on the Net-Policy project. There are a number of screen shots showing all the interfaces as well on the main net-policy web site.
MAGIC() PSEUDO-CODEA pseudo-code walk-through of the essential results of the magic() routine above is below. In a CGI script, for example, the magic() routine will be called multiple times (once per screen) but the results will be the same in the end -- it's all taken care of magically ;-).
################ ## WARNING: pseudo-code describing a process! Not real code! ################
# Loop through each primary and display the primary's questions.
while(primaries to process) {
display_primary_questions();
get_user_input();
check_results();
run_primary_post_answers();
}
# Displays a "will be doing these things" screen, # and has a commit button. display_commit_screen();
# Loop through each primary and run its actions.
# Note: see action documentation about execution order!
foreach (primary that was displayed) {
results = run_primary_actions();
display(results);
}
# If magic() is called again, it restarts from # the top primary again.
NEW() OPTIONSOptions passed to the QWizard new() operator define how the QWizard instance
will behave. Options are passed in the following manner:
new QWizard (option => value, ...)
Valid options are:
- QWizard::Generator::Best (default: picks the best available) - QWizard::Generator::HTML - QWizard::Generator::Gtk2 - QWizard::Generator::Tk - QWizard::Generator::ReadLine (limited in functionality)
The QWizard::Generator::Best generator is used if no specific generator is specified. The Best generator will create an HTML generator if used in a web context (i.e., a CGI script), or else pick the best of the available other generators (Gtk2, then Tk, then ReadLine).
This example forces a Gtk2 generator to be used:
my $wiz = new QWizard(generator => new QWizard::QWizard::Gtk2(),
# ...
);
new() method of the Generator that is
created.
See the bar documentation in the QUESTION DEFINITIONS section below for details on this field.
The WIDGETS are normal question widgets, just as can appear in the questions section of the primaries definition as described below.
In addition, however, there can be subgroupings with a title as well. These are then in a sub-array and are displayed with a title above them. EG:
leftside => [
{ type => 'button',
# ... normal button widget definition; see below
},
[
"Special Grouped-together Buttons",
{ type => 'button',
# ...
},
{ type => 'button',
# ...
},
],
],
The above grouped set of buttons will appear slightly differently and grouped together under the title ``Special Grouped-together Buttons''.
The widget-test-screen.pl in the examples directory shows examples of using this.
Important note: Not all backends support this yet. HTML and Gtk2 do, though.
The primaries argument of the new() function defines the list of questions that may be posed to a user. Each primary in the hash will contain a list of questions, answers, etc., and are grouped together by a name (the key in the hash). Thus, a typical primary set definition would look something like:
%my_primaries =
(
# The name of the primary.
'question_set_1' =>
# its definition
{
title => 'My question set',
questions =>
# questions are defined in an array of hashes.
[{type => 'checkbox',
text => 'Is this fun?',
name => is_fun,
default => 1,
values => [1, 0] },
{type => 'text',
text => 'Enter your name:',
name => 'their_name'}],
post_answers =>
# post_answers is a list of things to do immediately after
# this set of questions has been asked.
[ sub { print "my question set answered" } ],
actions =>
# actions is a list of actions run when all is said and done.
[ sub {
return "msg: %s thinks this %s fun.\n",
qwparam('their_name'),
(qwparam('is_fun')) ? "is" : "isn't"
}],
actions_descr =>
# An array of strings displayed to the user before they agree
# to commit to their answers.
[ 'I\'m going to process stuff from @their_name@)' ]
});
See the QWizard::API module for an alternative, less verbose, form of API for creating primaries which can produce more compact-looking code.
In the documentation to follow, any time the keyword VALUE appears, the following types of ``values'' can be used in its place:
- "a string"
- 10
- \&sub_to_call
- sub { return "a calculated string or value" }
- [\&sub_to_call, arguments, to, sub, ...]
Subroutines are called and expected to return a single value or an array reference of multiple values.
Much of the time the VALUE keyword appears in array brackets: []. Thus you may often specify multiple values in various ways. E.g., a values clause in a question may be given in this manner:
sub my_examp1 { return 3; }
sub my_examp2 { return [$_[0]..$_[1]]; }
values => [1, 2, \&my_examp1, [\&my_examp2, 4, 10]],
After everything is evaluated, the end result of this (complex) example will be an array passed of digits from 1 to 10 passed to the values clause.
In any function at any point in time during processing, the qwparam() function can be called to return the results of a particular question as it was answered by the user. I.e., if a question named their_name was answered with ``John Doe'' at any point in the past series of wizard screens, then qwparam('their_name') would return ``John Doe''. As most VALUE functions will be designed to process previous user input, understanding this is the key to using the QWizard Perl module. More information and examples follow in the sections below.
These are the tokens that can be specified in a primary:
The Question Definitions section describes valid question formatting.
if (some_condition()) {
$_[0]->add_todos('primary1', ...);
}
See the QWizard Object Functions section for more information on the add_todos() function, but the above will add the 'primary1' screen to the list of screens to display for the user before the wizard is finished.
A post_answers subroutine should return the word ``OK'' for it to be successful (right now, this isn't checked, but it may be (again) in the future). It may also return ``REDISPLAY'' which will cause the screen to displayed again.
For HTML output, these will be run just before the next screen is printed after the user has submitted the answers back to the web server. For window-based output (Gtk2, Tk, etc.) the results are similar and these subroutines are evaluated before the next window is drawn.
The collected values returned from the VALUES evaluation will be displayed to the user. Any message beginning with a 'msg:' prefix will be displayed as a normal output line. Any value not prefixed with 'msg:' will be displayed as an error (typically displayed in bold and red by most generators.)
sub { $_[0]->add_todos('subname1', ...); }
If specified, it should be a CODE reference which when executed should return a 1 if the primary is to be displayed or a 0 if not. The primary will be entirely skipped if the CODE reference returns a 0.
See the bar documentation in the QUESTION DEFINITIONS section below for details on this field.
Important note: See the leftside/rightside documentation for QWizard for more details and support important notes there.
Questions are implemented as a collection of hash references. A question generally has the following format:
{
type => QUESTION_TYPE
text => QUESTION_TEXT,
name => NAME_FOR_ANSWER,
default => VALUE,
# for menus, checkboxes, multichecks, ... :
values => [ VALUE1, VALUE2, ... ], # i.e., [VALUES]
# for menus, checkboxes, multichecks, ... :
labels => { value1 => label1, value2 => label2 } # i.e., [VALUES]
}
Other than this sort of hash reference, the only other type of question allowed in the question array is a single ``'' empty string. The empty string acts as a vertical spatial separator, indicating that a space should occur between the previous question and the next question.
The fields available to question types are given below. Unless otherwise stated, the fields are available to all question types.
The namespace for these names is shared among all primaries (except 'remapped' primaries, which are described later). A warning will be issued if different questions from two different primaries use the same name. This warning will not be given if the question contains an override flag set to 1.
The button_label clause can specify text to put right next to the checkbox itself.
If a backend supports key accelerators (GTk2): Checkbox labels can be bound to Alt-key accelerators. See QUESTION KEY-ACCELERATORS below for more information.
For example, the following clauses:
{
type => 'multi_checkbox',
name => 'something',
values => ['end1','end2'],
...
}
will give parameters of 'somethingend1' and 'somethingend2'.
If a backend supports key accelerators (GTk2): Checkbox labels can be bound to Alt-key accelerators. See QUESTION KEY-ACCELERATORS below for more information.
If a backend supports key accelerators (GTk2): Radio button labels can be bound to Alt-key accelerators. See QUESTION KEY-ACCELERATORS below for more information.
{
type => 'menu',
name => 'mymenu',
labels => [ 1 => 'my label1',
2 => 'my label2']
}
In this example, the user will see a menu containing 2 entries ``my label1'' and ``my label2'', but qwparam() will return 1 or 2 for qwparam('mymenu').
{
type => 'table',
text => 'The table:',
values => sub {
my $table = [['row1:col1', 'row1:col2'],
['row2:col1', 'row2:col2']];
return [$table];
}
}
This would be displayed graphically on the screen in this manner:
row1:col1 row1:col2
row2:col1 row2:col2
Additionally, a column value within the table may itself be a sub-table (another double-array reference set) or a hash reference, which will be a sub-widget to display any of the other types listed in this section.
Finally, a headers clause may be added to the question definition which will add column headers to the table. E.g.:
headers => [['col1 header','col2 header']]
The data field is processed early during display of the screen, so generation of large sets of data that won't always be downloaded or will take a lot of memor shouldn't use the data field. The data field is processed like any other value field where raw data or a coderef can be passed that will be called to return the data.
The datafn field should contain a CODE reference that will be called with five arguments:
{ type => 'filedownload',
text => 'Download a file:',
datafn =>
sub {
my $fh = shift;
print $fh "hello world: val=" . qwparam('someparam') . "\n";
}
},
Currently only Gtk2 supports this button, but others will in the future.
Additionally, the GD::Graph options can be specified with a graph_options tag to the question, allowing creation of such things as axis labels and legends.
Widget Options:
The function should return undef when no parent exists above the current node.
An example return array structure could look like:
[
'simple string 1',
'simple string 2',
{
name => 'myanswer:A',
label => 'Answer #A'
},
{
name => 'myanswer:B',
label => 'Answer #B'
},
]
The function should return undef when no children exist below the current node.
The button widget will be equivalent to pressing the next button. The next primary will be shown after the user presses the button.
If a backend supports key accelerators (GTk2): Button labels can be bound to Alt-key accelerators. See QUESTION KEY-ACCELERATORS below for more information.
+-------------------+-----------------+ | Question 1 | Answer Widget 1 | | Longer Question 2 | Answer Widget 2 | +-------------------+-----------------+
Adding a bar in the middle of these questions, however, would break the forced columns above into separate pieces:
+------------+------------------------+ | Question 1 | Answer Widget 1 | +------------+------------------------+ | BAR | +-------------------+-----------------+ | Longer Question 2 | Answer Widget 2 | +-------------------+-----------------+
Finally, there is an implicit top bar in every primary and the QWizard object as a whole. You can push objects onto this bar by adding objects to the $qwizard->{'topbar'} array or by adding objects to a primary's 'topbar' tag. E.G.
my $qw = new QWizard(primaries => \%primaries,
topbar => [
{
type => 'menu', name => 'menuname',
values => [qw(1 2 3 4)],
# ...
}]);
The widgets shown in the topbar will be a merge of those from the QWizard object and the primary currently being displayed.
TODO: make it work better with merged primaries
TODO: make a bottom bar containing the next/prev/cancel buttons
Note: This is not a secure way to hide information from the user. The data set using hidden are contained, for example, in the HTML text sent to the user.
Really, don't use this. It's for emergencies only. It only works with HTML output.
The values clause is not needed if the labels clause is present.
If the values clause is not specified and the labels clause is, the values to display are extracted from this labels clause directly. If a value from the values clause does not have a corresponding label, the raw value is presented and used instead. Generally, only the labels clause should be used with radio buttons, menus, or check boxes; but either or both in combination work.
The labels clause subscribes to all the properties of the VALUES convention previously discussed. Thus, it may be a function, an array of functions, or any other type of data that a VALUE may be. The final results should be an array, especially if the values clause is not present, as the order displayed to the user can be specified. It can also be a hash as well but the displayed order is subject to Perl keys() conventions and thus an array is preferred when no values clause has been defined.
qwparam() from
within the script). In the case of error or 'REDISPLAY', the current
primary screen will be repeated until the function returns 'OK'.
The arguments passed to the function are the reference to the wizard, a reference to the question definition (the hash), and a reference to the primary containing the question (also a hash.) The function should use the qwparam() function to obtain the value to check. An array can be passed in which the first argument should be the subroutine reference, and the remaining arguments will be passed back to the subroutine after the already mentioned default arguments.
There are a set of standard functions that can be used for checking values. These are:
If specified, it should be a CODE reference which when executed should return a 1 if the question is to be displayed or a 0 if not. The question will be entirely skipped if the CODE reference returns a 0.
As an example, Net-Policy uses this functionality to allow users to redisplay generated data tables and changes the column that is used for sorting depending on a menu widget.
Some generators (currently only Gtk2 actually) support key accelerators so that you can bind alt-keys to widgets. This is done by including a '_' (underscore) character where appropriate to create the binding. EG:
{
type => 'radio',
text => 'select one:',
values => ['_Option1','O_ption2', 'Option3']
}
When Gtk2 gets the above construct it will make Alt-o be equivelent to pressing the first option and Alt-p to the second. It will also display the widget with a underline under the character that is bound to the widget. HTML and other non-accelerator supported interfaces will strip out the _ character before displaying the string in a widget.
In addition, unless a no_auto_accelerators = 1> option is passed to the generator creation arguments, widgets will automatically get accelerators assigned to them. In the above case the 't' in Option3 would automatically get assigned the Alt-t accelerator (the 't' is selected because it hasn't been used yet, unlike the o and p characters). You can also prefix something with a ! character to force a single widget to not receive an auto-accelerator (EG: ``!Option4'' wouldn't get one).
A few QWizard parameters are special and help control how QWizard behaves. Most of these should be set in the primaries question sets using a hidden question type.
As an example, Net-Policy uses this functionality to allow users to redisplay generated graphs and maps that will change dynamically as network data are collected.
This token can also be set directly in a primary definition to affect just that primary screen.
The following parameters are used internally by QWizard. They should not be modified.
The following functions are defined in the QWizard class and can be called as needed.
The magic() routine exits only after all the primaries have been run up through their actions, or unless one of the following conditions occurs:
- $qw->{'one_pass'} == 1
- $qw->{'generator'}{'one_pass'} == 1
By default, some of the stateless generators (HTML) will set their one_pass option automatically since it is expected that the client will exit the magic() loop and return later with the next set of data to process. The magic() routine will automatically restart where it left off if the last set of primaries being displayed was never finished. This is common for stateless generators like HTTP and HTML.
magic() routine has
ended and you don't intend to call it again. Calling finished() will
remove the QWizard window from visibility.
However, this flag indicates that the actions of the childrens' primaries listed in this call are to be called before the current primary's actions.
Important note: you can not use both -remap (see below) and -merge at the same time! This will break the remapping support and you will not get expected results!
This is rather complex and is better illustrated through an example. There is an example that illustrates this in the QWizard Perl module source code examples directory, in the file number_adding.pl. This code repeatedly asks for numbers from the user using the same primary.
Important note: you can not use both -remap and -merge at the same time! This will break the remapping support and you will not get expected results!
qwparam('NAME')qwparam('NAME')QWizard parameters are accessible until the last screen in which all the actions are run and the results are displayed. Parameters are not retained across primary execution.
The qwparam() function is exported by the QWizard module by default, so the function shouldn't need to be called directly from the QWizard object. Thus, just calling qwparam('NAME') by itself will work.
qwpref('NAME')qwpref('NAME')
TBD: Document the $qw->add_hook and $qw->run_hooks methods that exist.
(basically $qw->add_hook('start_magic', \&coderef) will run coderef at the start of the magic function. Search the QWizard code for run_hooks for a list of hook spots available.
The variable $QWizard::qwdebug controls debugging output from QWizard. If set to 1, it dumps processing information to STDERR. This can be very useful when debugging QWizard scripts as it displays the step-by-step process about how QWizard is processing information.
Additionally, a qwdebug_set_output() function exists which can control the debugging output destination. Its argument should be a reference to a variable where the debugging output will be stored. Thus, debugging information can be stored to a previously opened error log file by doing the following:
our $dvar;
$QWizard::qwdebug = 1;
$qw->qwdebug_set_output(\$dvar);
$qw->magic('stuff');
print LOGFILE $dvar;
There are a few usage examples in the examples directory of the source package. These examples can be run from the command line or installed as a CGI script without modification. They will run as a CGI script if run from a web server, or will launch a Gtk2 or Tk window if run from the command line.
qwparam(), qwpref()
qw_required_field(), qw_integer(), qw_optional_integer(), qw_check_int_ranges(), qw_check_length_ranges(), qw_hex(), qw_optional_hex(), qw_check_hex_and_length()
Wes Hardaker, hardaker@users.sourceforge.net
For extra information, consult the following manual pages:
Example API call:
perl -MQWizard::API -MData::Dumper -e 'print Dumper(qw_checkbox("my ?","it",'A','B', default => 'B'));'
$VAR1 = {
'text' => 'it',
'name' => 'my ?',
'default' => 'B',
'values' => [
'A',
'B'
],
'type' => 'checkbox'
};
|
QWizard - Display a series of questions, get the answers, and act on the answers. |