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.