File: evans.html

package info (click to toggle)
lg-issue83 2-1
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 1,464 kB
  • ctags: 165
  • sloc: perl: 113; sh: 78; ansic: 70; python: 64; makefile: 35; asm: 16; awk: 7
file content (572 lines) | stat: -rw-r--r-- 24,660 bytes parent folder | download
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
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
<!--startcut  ==============================================-->
<!-- *** BEGIN HTML header *** -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML><HEAD>
<title>Saving Users From Themselves -or- Dealing with User Input in Python LG #82</title>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#0000AF"
ALINK="#FF0000">
<!-- *** END HTML header *** -->

<!-- *** BEGIN navbar *** -->
<IMG ALT="" SRC="../gx/navbar/left.jpg" WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom"><A HREF="lg_bytes.html"><IMG ALT="[ Prev ]" SRC="../gx/navbar/prev.jpg" WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="index.html"><IMG ALT="[ Table of Contents ]" SRC="../gx/navbar/toc.jpg" WIDTH="220" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../index.html"><IMG ALT="[ Front Page ]" SRC="../gx/navbar/frontpage.jpg" WIDTH="137" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue83/evans.html"><IMG ALT="[ Talkback ]" SRC="../gx/navbar/talkback.jpg" WIDTH="121" HEIGHT="45" BORDER="0" ALIGN="bottom"  ></A><A HREF="../lg_faq.html"><IMG ALT="[ FAQ ]" SRC="./../gx/navbar/faq.jpg"WIDTH="62" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="heriyanto.html"><IMG ALT="[ Next ]" SRC="../gx/navbar/next.jpg" WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom"  ></A><IMG ALT="" SRC="../gx/navbar/right.jpg" WIDTH="15" HEIGHT="45" ALIGN="bottom">
<!-- *** END navbar *** -->

<!--endcut ============================================================-->

<TABLE BORDER><TR><TD WIDTH="200">
<A HREF="http://www.linuxgazette.com/">
<IMG ALT="LINUX GAZETTE" SRC="../gx/2002/lglogo_200x41.png"
	WIDTH="200" HEIGHT="41" border="0"></A>
<BR CLEAR="all">
<SMALL>...<I>making Linux just a little more fun!</I></SMALL>
</TD><TD WIDTH="380">


<center>
<BIG><BIG><STRONG><FONT COLOR="maroon">Saving Users From Themselves<BR>
-or-<BR>
Dealing with User Input in Python</FONT></STRONG></BIG></BIG><BR>
<STRONG>By <A HREF="../authors/evans.html">Paul Evans</A></STRONG></BIG>

</TD></TR>
</TABLE>
<P>

<!-- END header -->




<p>You probably won't be using <a href="http://python.org/">Python</a> long
before writing a program which needs user input.  As a wide-eyed, innocent new
Python programmer, you may naively expect that you can simply ask users for
input and they will just give it to you....</p>

<BLOCKQUOTE><EM>
WARNING: Showing the preceding sentence to veteran programmers may
cause them to collapse on the floor giggling helplessly.
</EM></BLOCKQUOTE>

<p>Users don't work that way.</p>

<p>For example, if you ask for a simple 'y or n' response, your user may
cheerfully type in their name - or their lunch order, or nothing at all - and
your program will break. They don't do this on purpose (well, <i>mostly</i>). It's
just that the poor dears are easily distracted, totally ignore your carefully
worded input prompts and often type complete gibberish as far as your program
is concerned.  Next, oddly enough, they will blame you, the programmer. Then you will look
foolish and feel Unhappy.</p>

<p>To avoid this misery, the very first thing you need to do is make sure that
whatever comes back from the user is checked to see if it's even vaguely
close to what you expected. Python has heaps of functions to help you with
this and we'll begin by going through some of them together below.</p>

<p>Another thing you can do is use <i>validators</i> on your input widgets. The way
these work is they simply throw away any keystrokes that are not what you are
after. As an example, if you set a numeric validator on a string widget,
users can press 'ABC' etc. as much as they like and nothing will even show up
in the widget. The only keys they can press that will have any effect are 0-9
and, perhaps, a decimal or dollar symbol. We'll play with these too later on.</p>

<p>Finally, even if you are lucky enough to find yourself in possession of a
particularly well-trained and obedient user who always types what you ask,
the input is unlikely to be <i>formatted</i> exactly the way you want it. Careless
typing often produces strings like 'jOHN sMith' (caps lock) or phone numbers
resembling '604555-1212'.</p>

<p>All kidding aside, it's actually <i>your job</i> as a programmer to make it as
easy and fast as possible for the user to input data and that it be presented
and stored in a consistent format. Plus, you can get a great deal of personal
satisfaction and <i>even</i>, dare I say it? <i>gratitude</i> from users if you can save
them from the hell of properly typing something like a Canadian postal code.</p>

<h2>Acquiring Input</h2>

<p>First your program will need to acquire some user input. From the console
Python offers two methods for this 'raw_input("Prompt")' and 'input("Prompt")'.
(Don't use 'input', see below.) You can also get input from good ol' command
line arguments or environment variables.</p>

<p>Other, more graphical methods are available, without getting too carried away, such as
<a href="http://xdialog.free.fr">Xdialog</a>, Gdialog
(part of gnome-utils) or <a href="http://kaptain.sourceforge.net/">Kaptain</a>.</p>

<p>Access to full-blown GUI toolkits is available from Python using
<a href="http://www.riverbankcomputing.co.uk/pyqt/">PyQT</a>
, <a href="http://pmw.sourceforge.net">TKinter</a>,
<a href="http://wxpython.org">WxPython</a> and <a href="http://www.daa.com.au/~james/pygtk/">PyGTK</a>
among others.</p>

<p>This is probably a good time to provide a few words of caution. Most users
are contented, docile creatures who like to have their belly rubbed, but you
<i>will</i> encounter rogue types bent on destruction.</p>

<p>For this reason you must never allow user input to leak into your command space:</p>

<ul>
  <li><STRONG>Use 'raw_input()' instead of 'input()'.</STRONG>  'input()' is
  fed to 'eval()' before your program gets it.  This automatically converts
  types, which is convenient if you want an integer <EM>and</EM> the user accidentally
  gives you one.  But the user would have to quote strings.  Worse, a rogue user
  might type 'os.system("rm -r *")', which would give you a bad day.
  'raw_input()' returns whatever the user entered as a string.  This makes
  validation easier, because you know what type it will be and thus which
  operations you can apply to it.<p></li>
  <li><STRONG>Always check input to os.system(), os.popen() or os.exec*() calls.</STRONG>
<p></li>
  <li><STRONG>Always escape user input before printing it to a web page or using it in a
SQL query.</STRONG>  This is in addition to input checking.  Unescaped special
characters can cause invalid HTML, screw up your page formatting, and allow the user
to exploit Javascript "features" against another user.  Unescaped special characters in
a SQL query may cause a SQL syntax error or cause the query to do more
than intended.  Use 'cgi.escape()' to escape HTML.  See your database documentation
to escape SQL queries.
<p></li>
</ul>


<p>O.K. Relax. The spooky part is over.</p>

<p>Open an xterm and type 'python' to enter the interpreter. Note: Many of these
examples <i>require</i> that you be using a version of Python greater than or
equal to version '2'. Redhat still ships with version 1.5x as
default, so if you are a Redhat user you will need to type 'python2' instead
(and possibly install the rpm first from 'add-ons'). For the record, version
'1.5' was released in a year which began with the digits '1' and '9'.</p>

<h2>Checking the Content of String Objects</h2>

<p>Programming languages usually include methods for checking of this kind and
Python is no exception. Consider one of our first challenges as stated above:
making sure the user gives us a valid number when we ask for one.</p>

<p>It happens that all string objects in Python have built-in <i>methods</i>  which
make this quite painless. Type these lines in at the '&gt;&gt;&gt;' prompt:</p>

<pre>
&gt;&gt;&gt;input = '9'
&gt;&gt;&gt;input.isdigit()
1
</pre>

<p>This will return a '1' (true), so you can easily use it in an 'if' statement
as a condition. Some other handy attributes of this kind are:</p>

<pre>
s.isalnum() returns true if all characters in s are alphanumeric, false otherwise.
s.isalpha() returns true if all characters in s are alphabetic, false otherwise.
</pre>

<p>For a complete list of these and much more, I highly recommend the
<a href="http://www.brunningonline.net/simon/python/PQR2.1.html">Python 2.1 Quick Reference</a>. I use this all the time and even have an
older text version stuffed into <a href="http://hnb.sourceforge.net">HNB</a> for speed.</p>

<p>This will get us through simple cases like menu choices, but what if we
wanted a float or a real number?</p>

<p>Consider:</p>

<pre>
input = '9.9' or
input = '-9'
</pre>

<p>Both of these are valid numbers, but input.isdigit() will return '0' (false),
because the negative sign and the decimal point are not 'digits'. Our
poor user will be very confused when we spit back an error message if these
entries are valid.</p>

<p>So, let's <i>assume</i> that they are what we want and try to convert
them explicitly. For this we'll use the Python <i>try/except</i> construction.
Python raises <i>exceptions</i> of different kinds on errors and we can trap these
errors individually by name.</p>

<p>Say we wanted an integer like '-9', we can use the numeric operator 'int()'
to explicitly attempt the conversion for us.</p>

<pre>
try:
    someVar = int(input)
    print 'Is an integer'
except (TypeError, ValueError):
    print 'Not an integer'
</pre>

<p>Two things to notice here. The first is that we are checking for two
different exceptions, Type and Value. This way we not only handle the user
entering a float (like '9.9'), but we also allow for the possibility that they
didn't even enter a number of any kind - perhaps they entered 'Ham on rye'.
The second thing to notice is that <i>we actually entered the kinds of
exceptions we were interested in trapping.</i> It's very easy to just type in
open ended exceptions without bothering to look up which errors you are
trapping like this:</p>

<pre>
try:
    someVar = int(input)
    print 'Is an integer'
except:
    print 'Not an integer'
</pre>

<p>DO NOT DO THIS. Python will let you, but since you are now trapping <i>all</i>
exceptions debugging will be a nightmare for you if anything breaks. Just
trust me on this one; look up the errors you mean to trap and you'll save
time in the long run.</p>

<p>Other operators you'll find useful are long() and float(). On the flip side,
str() can convert anything to a string.</p>

<p>Don't forget to range check - it's no good congratulating yourself on
ensuring your program always gets an integer from a user if it blithely
accepts the integer '42' as a valid month day... Make sure the number falls
into the expected range using the comparison operators '&gt;, &lt;, &gt;=' etc.</p>

<h2>Validating Input</h2>

<p>As we've seen, we can validate input <i>after</i> we get it, but wouldn't it be
nice if we could prevent the user from entering mistakes in the first place?</p>

<p>Enter widget validators.</p>

<p>These are things built into graphical user interface toolkits that prevent
unwanted keystrokes from even <i>appearing</i> in the string widget. Toolkits usually
come with some built-in validators for numeric, alpha, and alphanumeric etc.
and are quite easy to use. I'm currently using mostly <a href="http://www.riverbankcomputing.co.uk/pyqt/">PyQT</a>
 for gui's, but <a href="http://pmw.sourceforge.net">TKinter</a>,
<a href="http://wxpython.org">WxPython</a> and even <a href="http://kaptain.sourceforge.net/">Kaptain</a>
all have validators. I could be wrong, but <a href="http://www.daa.com.au/~james/pygtk/">PyGTK</a>
seems not to have them - yet. Perhaps you could hook up a signal and roll your own if you happen to
use a toolkit that doesn't have them.</p>

<p>If the built-in validators don't suit you then PyQt, for example, allows you
to specify your own, custom validators.</p>

<p>Clearly, I can't go into detail for every toolkit out there, but here's an
example of how to attach a numeric validator to a widget in PyQT. The
widget's name is 'self.rate', we're attaching the 'QDoubleValidator' and
telling it to accept numbers between 0.0 and 999.0 up to 2 decimal places:</p>

<pre>
self.rate.setValidator(QDoubleValidator(0.0, 999.0, 2, self.rate) )
</pre>

<p>Nice eh? Notice it took care of range checking for us too!</p>

<p>Other ways to help users enter information include spinners, pick-lists and
combo-boxes, but you already knew that.</p>

<h2>Formatting Input</h2>

<p>Remember the 'jOHN sMith' example from the introduction? Here's the fix:</p>

<pre>
&gt;&gt;&gt;'jOHN sMith'.title()
'John Smith'
</pre>

<p>Yes, yet another attribute of all string objects in Python is the 'title()'
attribute which will helpfully capitalize each word for you. 'capitalize()'
is similar, but only does the first character:</p>

<pre>
&gt;&gt;&gt; 'jOHN sMith'.capitalize()
'John smith'
</pre>

<p>Go ahead and try 'upper()', 'lower()' and 'swapcase()' on your own if you
like. I think you can guess their behaviour.</p>

<p>But how about 'rjust(n)'? This is only one of some really handy attributes
you can use to layout reports. Watch:</p>

<pre>
&gt;&gt;&gt; 'John Smith'.rjust(15)
'     John Smith'
</pre>

<p>Our string has been right justified for us in a string 15 characters long.
Sweet. As you've probably guessed, there are also 'center(n)' and 'ljust(n)'.
Again, have a look at the
<a href="http://www.brunningonline.net/simon/python/PQR2.1.html">Python 2.1 Quick Reference</a>
to see them all.</p>

<p>Another, very important operator in Python is the '%' (per cent) operator.
The description of this in combination with list objects and printf-style
formatting codes could easily consume several pages, so I'm just going to gloss
over it with a few examples to pique your interest today.</p>

<p>In it's simplest form, the '%' operator lets you write, say, a proper sentence that includes variables which can change
at runtime:</p>

<pre>
&gt;&gt;&gt; 'This is a %s example of its %s.' % ('good', 'use')
'This is a good example of its use.'
</pre>

<p>At least, I hope it is. This is only the beginning of its power. In addition to just string object substitution with
'%s' there is also '%r' and the printf friends from the 'C' language:
c, d, i, u, o, x, X, e, E, f, g, G.</p>

<p>Here's an example from <a href="http://www.brunningonline.net/simon/python/PQR2.1.html">Python 2.1 Quick Reference</a>:</p>

<pre>
&gt;&gt;&gt; '%s has %03d quote types.' % ('Python', 2)
'Python has 002 quote types.'
</pre>

<p>The right hand side may also be a mapping, which allows you to refer to fields by name.
</p>

<p>Let's move on to something a little more challenging, but common enough.</p>

<h3>Phone Numbers</h3>

<p>Phone numbers are variable in length. Sometimes they are only 2 or 3 digits
long if you are behind a corporate PBX system. Other times they might stretch
out to 15 digits or more for international calling. They <i>might</i> even contain
'#' symbols or asterisks. Maybe even commas. Worse, the user may attempt to
impose a format on it as they enter it. Or a partial format. Or not.</p>

<p>Now, it will only frustrate your user if you don't let them at least try to
enter it properly, so your validator had better accept all of #, *, 'comma',
-, ), ( as well as the digits 0-9. Of course, you could still end up with:</p>

<pre>
'250-(555)-12-12'
</pre>

<p>instead of the string:</p>

<pre>
'(250) 555-1212'
</pre>

<p>that we actually want (for a North American phone number anyhow). Don't
worry, we'll make the solution generic enough to handle just about anything.</p>

<p>My first instinct when I need something like this is to copy someone else's
work by mining <a href="http://www.google.com">Google</a> - especially <a href="http://www.google.com">Google Groups</a>. This turns out to
be a good instinct for me to have since the code snippet I usually find will
be far better than I could do on my own. Unfortunately, this time I turned up
an email from Guido van Rossum (the inventor of Python) explaining to someone
that Python did not have such a thing and perhaps they could use something
like:</p>

<pre>
import string
def fmtstr(fmt, str):
    res = [] i = 0
    for c in fmt:
        if c == '#':
            res.append(str[i:i+1]) i = i+1
        else:
            res.append(c)
    res.append(str[i:])
    return string.join(res)
</pre>

<p>This is a darn good start of course and you can't argue with the credentials of
its author, but it doesn't handle all the cases without a lot of 'if/then'
constructs to count how many digits you were given in order to choose a format
string of the correct length. Go ahead and paste it
into your xterm and then call it like this:</p>

<pre>
&gt;&gt;&gt; fmtstr('###-####', '5551212')
'5 5 5 - 1 2 1 2 '
</pre>

<p>In fact, I did copy and paste it into my editor and then constructed a long sequence of
'if/thens' for phone numbers, dates and other types of entries, but I still wasn't
handling everything. Plus, I had dozens and dozens of lines doing self-similar
things. They have since passed on to their reward.</p>

<p>O.K., here we go... First, let's filter any "extra" formatting characters we
let the user type in:</p>

<pre>
def filter(inStr, allowed):
    outStr = ''
    for c in inStr:
        if c in allowed:
            outStr += c
    return outStr
</pre>

<p>We could call it like this:</p>

<pre>
&gt;&gt;&gt;filter('250-(555)-12-12', string.digits)
'2505551212'
</pre>

<p>Or we could define the second argument ourselves as '0123456789#*,' to
include all the allowable characters possible.</p>

<p>Now we just take Guido's code snippet and (this is the good bit) <i>reverse</i>
both the input arguments. This way we can specify just one long format string
and it will be matched until we run out of input. Any extra input will just
get tacked on, so we will never lose any characters.</p>

<pre>
# import the regular expression module
import re

def formatStr(inStr, fmtStr, p = '^'):
    inList = [x for x in inStr] #list from strings..
    fmtList = [x for x in fmtStr]
    # the good bit
    inList.reverse(); fmtList.reverse()
    outList = []
    i = 0
    for c in fmtList:
        if c == p:
            try:
                outList.append(inList[i])
                i += 1
            # break if fmtStr longer than inStr
            except IndexError:
                break
        else:
            outList.append(c)
    # handle inStr longer than fmtStr
    while i &lt; len(inList):
        outList.append(inList[i])
        i += 1
    # put it back the way we found it
    outList.reverse()
    outStr = ''.join(outList)
    # remove stray parens/- etc
    while re.match('[)|-| ]', outStr[0]):
        outStr = outStr[1:]
    # close any legit parens
    while outStr.count(')') &gt; outStr.count('('):
        outStr = '(' + outStr
    return outStr
</pre>

<p><a href="misc/evans/fmtstr.py.txt">[Text version of this listing.]</a></p>

<p>It's basically the same as Guido's except the default placeholder character is now
a '^' (caret), because we may need to use the '#'. Alternatively, this may be
specified as an, optional, third argument if we ever need real carets in the output.</p>

<p>Here's some sample output:</p>

<pre>
&gt;&gt;&gt; formatStr('51212', ' ^^^ ^^ (^^^) ^^^-^^^^')
'5-1212'
&gt;&gt;&gt; formatStr('045551212', ' ^^^ ^^ (^^^) ^^^-^^^^')
'(04) 555-1212'
&gt;&gt;&gt; formatStr('16045551212', ' ^^^ ^^ (^^^) ^^^-^^^^')
'1 (604) 555-1212'
&gt;&gt;&gt; formatStr('1011446045551212', ' ^^^ ^^ (^^^) ^^^-^^^^')
'1 011 44 (604) 555-1212'
</pre>

<p>In practice, you'll probably want to simply define your phone formatting
string early on e.g.:</p>

<pre>
phone_format_str = ' ^^^ ^^ (^^^) ^^^-^^^^'
</pre>

<p>There's a space at the beginning of the string so that any additional characters won't get smooshed onto it. You'd likely call it thus:</p>

<pre>
formatStr(input, phone_format_str)
</pre>

<p>... after you clean up your 'input' with something like the 'filter()' function.</p>

<h3>Postal Codes</h3>

<p>In case you are (blessedly) unfamiliar with Canadian postal codes, they look like
this:</p>

<pre>
'V8G 4L2'
</pre>

<p>Which <i>appears</i> innocuous enough until you attempt to type it. Especially
for non-typists (like me). You can turn on the caps lock - and then forget
to turn it off - or you have to type [shift]+alpha, number, [shift]+alpha
etc. and quite often end up with: 'v*g $l@' when you get out of sequence.
Needless to say, users <i>hate</i> typing them in and they hardly ever
look right. Mostly your application won't even capture postal codes, because
users simply won't bother. Some other countries have similar post codes. Shame.</p>

<p>Now, with our new formatting function, they're a piece of cake. First, we either
validate or filter whatever they give us, then we simply use Python's
built-in string attribute 'upper()' to set the case of the alpha characters
properly, finally:</p>

<pre>
&gt;&gt;&gt;formatStr('V8G4L2', ' ^^^ ^^^')
'V8G 4L2'
</pre>

<p>If accurate postal codes are critical to your application, you will need to do
more verification by way of counting the characters and verifying the pattern. For
general use though, you need to allow for the postal codes of other countries. I
think I normally format only if the number of characters == 6 after clean up.</p>

<p>How about Social Insurance Numbers? Same deal:</p>
<pre>
&gt;&gt;&gt; formatStr('716555123', '^^^-^^^-^^^')
'716-555-123'
</pre>

<p>You should run a check digit routine over Social Insurance Numbers first to
ensure they are valid. Ditto for credit cards.</p>

<p>I hope these examples will save you some time in coding user interfaces. I'd
very much like to <a href="mailto:pevans@catholic.org?subject=LG article on User Input">hear back</a>
with examples or improvements of your own. Particularly ways of dealing with dates<SUP><A HREF="#foot1">1</A></SUP> with users. They're always fun.</p>

<p>By the way, it's very important that you not keep these formatting aids a secret from your users. Put it in the 'help', use
'tooltips' or 'whatis' to let them know the facility is there for them. If they find out after months of typing things the long way,
they are liable to pout and you'll end up wasting afternoon coffee scratching them behind the ears (morning coffee is a given).</p>

<p>Have fun with it!</p>

<A name="foot1">1</A> That's <i>calendar</i> dates...





<!-- *** BEGIN bio *** -->
<SPACER TYPE="vertical" SIZE="30">
<P>
<H4><IMG ALIGN=BOTTOM ALT="" SRC="../gx/note.gif">Paul Evans</H4>
<EM>Paul Evans loves everything about electronics and computers in particular. He
is old enough to remember drooling over an Altair 8080A in his adolescence.
He and his two children live in the Wilds of Northern British Columbia;
they're not lumberjacks, but they're OK.</EM>


<!-- *** END bio *** -->

<!-- *** BEGIN copyright *** -->
<hr>
<CENTER><SMALL><STRONG>

Copyright &copy; 2002, Paul Evans.
Copying license <A HREF="../copying.html">http://www.linuxgazette.com/copying.html</A><BR> 
Published in Issue 82 of <i>Linux Gazette</i>, September 2002</H5>
</STRONG></SMALL></CENTER>
<!-- *** END copyright *** -->
<HR>

<!--startcut ==========================================================-->
<CENTER>
<!-- *** BEGIN navbar *** -->
<IMG ALT="" SRC="../gx/navbar/left.jpg" WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom"><A HREF="lg_bytes.html"><IMG ALT="[ Prev ]" SRC="../gx/navbar/prev.jpg" WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="index.html"><IMG ALT="[ Table of Contents ]" SRC="../gx/navbar/toc.jpg" WIDTH="220" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../index.html"><IMG ALT="[ Front Page ]" SRC="../gx/navbar/frontpage.jpg" WIDTH="137" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue83/evans.html"><IMG ALT="[ Talkback ]" SRC="../gx/navbar/talkback.jpg" WIDTH="121" HEIGHT="45" BORDER="0" ALIGN="bottom"  ></A><A HREF="../lg_faq.html"><IMG ALT="[ FAQ ]" SRC="./../gx/navbar/faq.jpg"WIDTH="62" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="heriyanto.html"><IMG ALT="[ Next ]" SRC="../gx/navbar/next.jpg" WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom"  ></A><IMG ALT="" SRC="../gx/navbar/right.jpg" WIDTH="15" HEIGHT="45" ALIGN="bottom">
<!-- *** END navbar *** -->
</CENTER>
</BODY></HTML>
<!--endcut ============================================================-->