1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
|
<section id="formbroker">
<title>The Form Broker</title>
<section>
<title>Introduction</title>
<para>
The <command>FormBroker</command> package creates instances of
objects representing a form data description. These objects offer a
simple interface of methods meant to validate and control data
as typically posted through an HTML form,
thus data represented through the association of form variables
with their values as returned, for example,
by the <command>::rivet::load_response</command> command
</para>
<note>
The <command>FormBroker</command> package is still experimental.
Basic functionalities and interface are not likely to change but
internal details and implementation could be redesigned
in future releases. More specifically the external validator mechanism
could be improved with the purpose of shielding the <command>FormBroker</command>
internals from a data validation procedure.
</note>
<!-- programlisting>
</programlisting -->
</section>
<refentry id="fb">
<refnamediv>
<refname>FormBroker</refname>
<refpurpose>
Form broker object creator
</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>::FormBroker</command>
<arg choice="plain">create</arg>
<arg><option>-quoting quoting_procedure</option></arg>
<arg><option>variable1 descriptor</option></arg>
<arg><option>variable2 descriptor</option></arg>
<arg>...</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
The command returns a reference to a form broker object by creating
a representation of the form data using the list of variable
descriptors passed to <command>create</command>. Each descriptor
is a list of parameter or parameter-value pairs whose order has as only requirement
to begin with the <command>{variable_name variable_type}</command> pair.
A formbroker object handles natively integer, unsigned, string and email data types.
The programmer can defined new data type and provide in the descriptor a
reference to a validating procedure for that type.
</para>
<para>
The optional <arg>-quoting quoting_procedure</arg> switch assigns a procedure to
be called to quote the form response values. The quoting procedure is any
procedure accepting a single string argument and returning its quoted value. A most
basic example is the FormBroker default quoting procedure
</para>
<programlisting>proc force_quote {str} {
return "'$str'"
}</programlisting>
<para>
Other parameters of a descriptors are
</para>
<itemizedlist>
<listitem><command>type</command>: the data type of the variable</listitem>
<listitem><command>bounds</command>: limits of a variable value. The
meanining of bounds depends on the variable type. For an integer is the
maximum absolute value for that variable (for an unsigned the lower
limit is invariably 0), for a string is the maximum length of the string. The
parameter bounds has no effect on an email data type
</listitem>
<listitem><command>constrain</command>: boolean value telling the variable has to be
forced to fulfill the constrain imposed by <command>bounds</command>. This field
is bidirectional in that it can be used by the validator to force the
variable value rewriting</listitem>
<listitem><command>validator</command>: name of the specialized validator for this variable</listitem>
<listitem><command>default</command>: default value of the variable if not set in a response array.
When a variable is given a default value the form validation will not fail on the fact that
this variable may be missing from the form response array</listitem>
<listitem><command>quote</command>: the variable value has to be quoted when written back in
the response array</listitem>
<listitem><command>validator</command>: name of the validator procedure. The procedure
can be any Tcl procedure accepting as argument the name of a dictionary
holding the variable internal representation.
</listitem>
</itemizedlist>
<para>
An example of a form accepting four variable, one for each native type of a form broker object
</para>
<programlisting> % set fbroker [::FormBroker create {var1 integer} {var2 unsigned} {var3 string} {var4 integer bounds {-10 100}}]
::FormBroker::form0</programlisting>
</refsect1>
<refsect1>
<title>Form broker object methods</title>
<para>
The central method of a form broker object is <command>validate</command>
</para>
<variablelist>
<varlistentry>
<listitem>
<cmdsynopsis>
<arg choice="plain"><replaceable>formBroker_object</replaceable></arg>
<arg choice="plain">validate</arg>
<arg><option>-forcequote</option></arg>
<arg choice="plain">response</arg>
<arg><replaceable>response copy</replaceable></arg>
</cmdsynopsis>
<para>
The method <command>validate</command> takes as argument the name of an array of variables
in the way this is produced by command <xref linkend="load_response">::rivet::load_response</xref>
returning a form response. The optional argument <replaceable>-forcequote</replaceable> causes the
variable values to be rewritten and quoted. If the optional argument <replaceable>response copy</replaceable>
is present the validated response is copied in this array instead of the input <arg choice="plain">response</arg>
array.
</para>
<para>
If the form data have been validated the method <command>validate</command> returns <emphasis>true</emphasis>
</para>
<para>
Example of form data validation (assuming ::rivet::load_response is loading the array <emphasis>response</emphasis>
with data taken from a form non displayed here)
</para>
<programlisting>% set fbroker [::FormBroker create {var1 integer} {var2 unsigned} {var3 string} {var4 integer bounds {-10 100}}]
::FormBroker::form0
% ::rivet::load_response
% parray response
response(var1) = -10
response(var2) = 20
response(var3) = a string
response(var4) = 50
# let's keep a copy of the response
% array set response_copy [array get response]
# form data validation
% $fbroker validate response
true
% $fbroker validate -forcequote response
% parray response
response(var1) = '-10'
response(var2) = '20'
response(var3) = 'a string'
response(var4) = '50'
# restore response original value
% array set response [array get response_copy]
% $fbroker validate -forcequote response response_copy
true
% parray response
response(var1) = -10
response(var2) = 20
response(var3) = a string
response(var4) = 50
% parray response_copy
response_copy(var1) = '-10'
response_copy(var2) = '20'
response_copy(var3) = 'a string'
response_copy(var4) = '50'
# a form object has to be destroyed if it's not needed anymore
% $fbroker destroy</programlisting>
</listitem>
</varlistentry>
<varlistentry>
<listitem>
<cmdsynopsis>
<arg choice="plain"><replaceable>formBroker_object</replaceable></arg>
<arg choice="plain">failing</arg>
</cmdsynopsis>
<para>
In case the validation fails method <command>failing</command> returns a list
of <emphasis>variable_name - error_condition</emphasis> pairs for each
variable whose value failed to validate and was impossible to fix. This list
is suitable to populate an array or used directly as a dictionary
</para>
<programlisting>% package require formbroker
1.0
% set fbroker [::FormBroker create {var1 integer} \
{var2 unsigned} \
{var3 string} \
{var4 integer}]
::FormBroker::form0
% ::rivet::load_response
# let's suppose we have an incomplete response
% parray response
response(var1) = '100'
response(var2) = '20'
response(var3) = 'a string'
% $fbroker validate response
false
$fbroker failing
var4 MISSING_VAR
# this can be prevented by assigning a variable a default value
% set fbroker [::FormBroker create {var1 integer} \
{var2 unsigned} \
{var3 string} \
{var4 integer default 0}]
::FormBroker::form1
% $fbroker validate response
true
% parray response
response(var1) = 100
response(var2) = 20
response(var3) = a string
response(var4) = 0
% set fbroker [::FormBroker create {var1 integer} \
{var2 unsigned} \
{var3 string length 10 constrain} \
{var4 integer bounds {-10 100}}]
::FormBroker::form2
% ::rivet::load_response
# this time the response has invalid data
% parray response
response(var1) = 'aaaaa'
response(var2) = '-20'
response(var3) = 'a longer string that breaks the 10 chars max limit imposed'
response(var4) = '150'
% $fbroker validate response
false
% $fbroker failing
var1 NOT_INTEGER var2 FB_OUT_OF_BOUNDS var4 FB_OUT_OF_BOUNDS</programlisting>
<para>
Notice that even though $response(var3) exceeds the 10 characters max length imposed to variable <emphasis>var3</emphasis>
this variable is not in the list returned by <command>failing</command> because
the 'constrain' attribute forced the truncation of the string.
In fact this applies also to the integer and unsigned values
</para>
<programlisting>% set fbroker [::FormBroker create {var1 integer bounds 10 constrain} \
{var2 unsigned constrain} \
{var3 string length 10 constrain} \
{var4 integer bounds {-10 100} constrain}]
::FormBroker::form0
% ::rivet::load_response
% parray response
response(var1) = abcdef
response(var2) = -20
response(var3) = a longer string that breaks the 10 chars max limit imposed
response(var4) = 150
% $fbroker validate response response_copy
false
% $fbroker failing
var1 NOT_INTEGER
% parray response_copy
response_copy(var2) = 0
response_copy(var3) = a longer s
response_copy(var4) = 100</programlisting>
<para>
The variable <emphasis>var1</emphasis> could not be constrained because the input
value "abcdef" is fundamentally incompatible
</para>
</listitem>
</varlistentry>
<varlistentry>
<listitem>
<cmdsynopsis>
<arg choice="plain"><replaceable>formBroker_object</replaceable></arg>
<arg choice="plain">response</arg>
<arg><option>response_array_name</option></arg>
</cmdsynopsis>
<para>
The <command>response</command> method fills
the array whose name is passed as optional argument
with the last response processing. If this argument is omitted
the method creates an array named <emphasis>response</emphasis>.
</para>
<para>
This method can be called also if no form response validation has taken place: it
simply populates the array with the default values assigned to the form variables. As
such is a way to create form default arrays to initialize forms created with
the <xref linkend="form_package">form</xref> package.
</para>
<programlisting>set fbroker [::FormBroker create {var1 integer default 0} \
{var2 unsigned default 1} \
{var3 string} \
{var4 integer default 0}]
% $fbroker response a
% parray a
a(var1) = 0
a(var2) = 1
a(var4) = 0</programlisting>
</listitem>
</varlistentry>
<varlistentry>
<listitem>
<cmdsynopsis>
<arg choice="plain"><replaceable>formBroker_object</replaceable></arg>
<arg choice="plain">reset</arg>
</cmdsynopsis>
<para>
The method resets the object to its initial defaults
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Writing a special variable form validator</title>
<para>
The form broker is by no means restricted to work only with its native
data types: you may define your own form variable types and have
them validated with their own variable validator.
</para>
<para>
A validator is a function accepting a dictionary as single argument and
must return either FB_OK, if the variable value is valid,
or any other used defined error code. The dictionary argument stores
the variable descriptor used internally by the form broker. The dictionary
keys are listed
</para>
<para>
Suppose you're writing an HTML that accept as input a network
interface MAC address.
A mac address is represented by 6 hexadecimal octets separated by
either a <quote>-</quote> (Windows convention) or <quote>:</quote>
(Unix, Mac convention). The procedure <command>validate_mac</command>
checks the validity of the mac address and if compliant transforms its
representation in the Unix form. By setting the key <quote>constrain</quote>
in the dictionary <emphasis>mac_address_d</emphasis> the procedure
is telling the form broker to copy the transformed value back
in the input response array
</para>
<programlisting>proc validate_mac {_mac_address_d} {
upvar $_mac_address_d mac_address_d
dict with mac_address_d {
set var [string trim $var]
if {[regexp {^[[:xdigit:]]{2}([:-][[:xdigit:]]{2}){5}$} $var]} {
set var [string tolower $var]
# we normalize the mac address to the Unix form.
# The dash '-' characters in the windows representation
# are replaced by columns ':'
set var [regsub -all -- {-} $var :]
# the 'constrain' field is bidirectional:
# it tells the validator to curb/change the value
# within bonds/forms/representation. By setting it the
# validator we tell the FormBroker to copy the value
# back in the response array
set constrain 1
return FB_OK
} else {
return FB_WRONG_MAC
}
}
}
% set fbroker [::FormBroker create {mac mac_address validator validate_mac}]
% ::rivet::load_response r
% parray r
r(mac) = 00-A1-B2-C3-D4-C5
% $fbroker validate r
true
% parray r
r(mac) = 00:a1:b2:c3:d4:c5</programlisting>
</refsect1>
</refentry>
</section>
|