Application
Design: Getting Your Users To Alert Themselves
Andrew MacNeill, AKSEL
Every application needs a way of notifying users
about something. The first "something" that may come to mind is an
error, which unfortunately, needs a very special kind of alert or handler. But there are lots of other events that users need to be aware of,
whether you as the developer plan for them or not. In January 2007 issue
of FoxPro Advisor, Mike Lewis wrote about a forced system shutdown
"event" that would let administrators get all users out of the
system. If you are running a process that may take a while, you might need to
let users know when it has been completed. If you have a large application,
there could be many events that require user notification. Perhaps worse, there
may be many events that your users could have reacted to, "if only they
had known" before something was happening. Some applications rely on
reports to display this information – but why waste paper when you can simply
alert users to an event?
For example, consider an accounting system. If you
are tracking invoices, users may want to know when they have many unpaid
invoices. Users may identify certain customers as being critical and want to
know when these customers place orders. In an ideal world, these types of
events would be noted early on in the design process and could be built into
the product from the start. But as applications mature, new requirements come
up. This article isn't about how you alert your users – but rather how you can
get your users to alert themselves (see the sidebar and sample code for using
the new VFPX Desktop Alert project).
Basic
Alerts
An alert has a minimum of two properties: a
trigger condition and the message to display. Since most applications these
days are multi-user, we'll also add a user field to identify which user should
see the alert. The Alerts table looks like this:
CREATE
TABLE ALERTS ;
( cID C(10), ;
cTitle C(30),
;
mText M,;
mCondition M,;
cUser C(15) )
Let's create a basic alert that looks for orders
that have not shipped by their requirement date using the ORDERS sample table
(found in your VFP Home directory under SAMPLES\DATA).
Add the following record into the Alerts table:
Field |
Value |
CID |
SAMPLE1 (unique Identifier) |
CTITLE |
Orders need shipping |
MTEXT |
There are orders that need shipping! |
MCONDITION |
LOCAL la(1) SELECT COUNT(*) FROM ORDERS ; WHERE
EMPTY(shipped_on) AND ;
require_by<DATE() ; INTO
ARRAY la RETURN la(1)>0 |
CUSER |
(blank) |
The entire Alert system can be managed via a Timer
control.
DEFINE
CLASS tmrAlert AS Timer
Interval
= 10000
cUser
= ""
PROCEDURE
Init
THIS.cUser
= GETENV("USERNAME")
ENDPROC
PROCEDURE
Timer
THIS.CheckAlerts()
ENDPROC
PROCEDURE
CheckAlerts
LOCAL
lnArea
lnArea = SELECT()
LOCAL
lcCondition
LOCAL
llRet
IF
NOT USED("ALERTS")
USE ALERTS IN 0
ENDIF
SELECT
ALERTS
SCAN
FOR EMPTY(cuser) OR cUser = lcUser
lcCondition = mcondition()
llRet = EXECSCRIPT(lcCondition)
IF llRet
** Display Alert
THIS.ShowAlert(TRIM(mText))
ENDIF
ENDSCAN
SELECT (lnArea)
ENDPROC
PROCEDURE
ShowAlert (tcText AS String)
WAIT WINDOW TRIM(tcText)
ENDPROC
ENDDEFINE
The initial code for CheckAlerts assumes you would
check alerts one at a time and then display them. An alternate approach would be store all of
the alert responses into a larger string and then display them. This approach
isn't always recommended if you decide to add further interactivity as shown
later on.
lcText
= ""
SCAN
FOR EMPTY(cuser) OR cUser = lcUser
lcCondition = mcondition()
llRet = EXECSCRIPT(lcCondition)
IF
llRet
** Display Alert
lcText = lcText + TRIM(mtext) +
CHR(13)+CHR(10)
ENDIF
ENDSCAN
IF
NOT EMPTY(lcText)
THIS.ShowAlert(lcText)
ENDIF
Note the use of EXECSCRIPT to execute the
condition. The call to the condition and the resulting text could be further
expanded by using the TEXTMERGE function. For example, the condition might be
dependent on the user id (stored in a variable named gcUserID).
Add a variable named pnTotal to the CheckAlerts
function within the SCAN loop and update it with whatever the result returns.
LOCAL
la(1)
SELECT
COUNT(*) FROM ORDERS ;
WHERE EMPTY(shipped_on)
AND ;
require_by<DATE()
AND emp_id = gcUserID ;
INTO ARRAY la
pnTotal = la(1)
RETURN
la(1)>0
Now change the mtext to
There are <<pnTotal>> orders that need
shipping!
The resulting text could even be more personalized
with:
Hey << GETENV("USERNAME")
>>, there are <<pnTotal>> orders that need shipping!
Acting on Alerts
Now that you've got an alert, what should the user
do with it? Add a memo field called mAction to the Alerts table and you can now
build in interactivity with your alert system. The following code uses a basic
MESSAGEBOX dialog to prompt the user with the details.
PROCEDURE
ShowAlert(tcText)
IF NOT EMPTY(Alerts.maction)
IF MESSAGEBOX(tcText
+" Do you want to view this?",4+32)=6
=execscript(alerts.maction)
ENDIF
ELSE
MESSAGEBOX(tcText,48)
ENDIF
ENDPROC
If your alert system combines alerts into a larger
text dialog, the MESSAGEBOX approach may not work. Instead, consider creating a
temporary cursor with the resulting text and the actions and allowing users to
review them. For example,
**
Updated CheckAlerts
CREATE
CURSOR alertResults (mText M, mAction M)
SELECT
ALERTS
SCAN
FOR EMPTY(cuser) OR cUser = lcUser
lcCondition = mcondition()
llRet = EXECSCRIPT(lcCondition)
IF llRet
** Display Alert
THIS.AddAlert(TRIM(alerts.mText),alerts.mAction)
ENDIF
ENDSCAN
THIS.ShowAlerts()
PROCEDURE
AddAlert
PARAMETERS
tctext,tcAction
INSERT
INTO alertResults VALUES (tcText, tcAction)
ENDPROC
PROCEDURE
ShowAlerts
SELECT
AlertResults
**
Display to user and display them.
ENDPROC
Building the User Interface
Now that we've got the basics of an alter
notification system built, how does the user create them? We could arrange for
a system administrator to learn how to write SQL code but a better approach
might be to create some alert templates that users could then use to add and
create their own alerts.
In my application, I set up two choices: one was
to view an Alert without the underlying SQL and one with the condition. This
way, beginners could rely on the templates without seeing the underlying code
or they could view the SQL and customize it themselves.
Create a separate table (I named mine "ALERTDEF.DBF")
that has a similar structure to the ALERTS table. Fill it with commonly used
rules or alerts that you believe might prove valuable. There's one trick here
that I find very useful.
There may be alerts that require some custom
values to be entered. For example, one user wants to know when they have
invoices that are over 30 days old while another may want to know invoices over
60 days old. There's no need to create two alerts. Instead, in the rule
template, use the STREXTRACT function to prompt the user for some values.
Here's a sample condition:
SELECT
COUNT(*) FROM ORDERS WHERE require_by<DATE( ) -
<?>How many days</?> INTO ARRAY la
When the user selects this template, the code to
add it to the Alerts table would be:
SCATTER
MEMVAR MEMO
lcCondition = m.mcondition
IF
"<?>"$lcCondition
lcPrompt =
STREXTRACT(lcCondition,"<?>","</?>")
lcRes = INPUTBOX(lcPrompt)
lcCondition =
STRTRAN(lcCondition,"<?>"+lcPrompt+"</?>",
lcRes)
ENDIF
m.mcondition = lcCondition
INSERT
INTO ALERTS FROM MEMVAR
Integrating
with your application
The examples so far rely on using a timer within
your application that triggers the alert. The problem with using Timers is that
unless you remember to enable and disable them, they can slow regular
operations down. Take the Alert system one step further by having it run
outside of your application's regular desktop either by creating a separate
thread (as described by Christof in September 2006's Advisor Answers) or as a
separate "mini-application". When your main application starts, call
the alert application and have it reside in the system tray all by itself.
Where
to go from here
Alerts are becoming a more important part of both
online and desktop applications and there are lots of ways of notifying the
user about them – that's one of the reasons why I don't provide one single way
of displaying the alert. Some of my applications use email for notifications
while others display alerts in a toolbar. Using customizable alerts with
templates lets users identify their own needs and hopefully reduce the amount
of redesign work in your application when a new requirement comes up.