This page uses CSS, and renders properly in the latest versions of Microsoft Internet Explorer and Mozilla.
This is Frontier: The Definitive Guide by Matt Neuburg, unabridged and unaltered from the January 1998 printing.
It deals with the freeware Frontier 4.2.3; for commercial Frontier 8, look here, and for the inexpensive Radio UserLand, look here. See my Web site for information.
Those wishing an offline or printed copy may purchase the book, which is still in print.
Copyright 1998 O'Reilly & Associates, Inc. ALL RIGHTS RESERVED. Reprinted with permission.
TOP UP: Interface NEXT: Pictures

27

Agents and Hooks

An agent is a script that Frontier calls automatically from time to time in the background. A hook is a script that Frontier calls automatically when a certain high-level event takes place. Together, they let your scripts respond to what's happening around them.

Agents

Agents run in a thread of their own; the agent thread number is maintained at system.compiler.threads.agents. The agent thread is present even if Frontier is otherwise idle. See Chapter 21, Yielding, Pausing, Threads, and Semaphores.

Agent scripts live at system.agents . The database, as shipped, already includes several of them, which you are free to examine. Agents have no eponymous handler; Frontier calls them without parameters.

An agent script is called by Frontier repeatedly, at intervals. The default interval is one second after the agent script finishes its previous execution; or the agent script itself may tell Frontier to wait longer before calling it again, by calling clock.sleepFor() , which takes as its parameter the number of seconds to wait before calling again.

The call to clock.sleepFor() does not have to occur in the agent script itself; but it must occur either in the agent script or in a script called by the agent script. For example, the script at system.agents.schedulerMonitor calls scheduler.monitor(), which calls clock.sleepFor(). The call to clock.sleepFor() need not come last in its script, and it does not immediately return control to Frontier.

Writing and Debugging Agents

To create an agent, you can just make a new script in system.agents, or choose New Script from the Main menu and set the dialog popup to Agent - this creates the script in system.agents for you and supplies a call to clock.sleepFor().

Testing an agent script requires some caution. You probably don't want Frontier to call the agent script while you're working on it. If you press the Compile button (or close the script edit window), Frontier will call the agent script one second later - so don't press it! You can, however, press the Run button to get implicit compilation; this lets you check that the script compiles and behaves as expected, but does not cause Frontier to call the script automatically as an agent. (See Chapter 13, Running and Debugging Scripts.)

You can't test an agent script by pressing its edit window's Run button if the script contains a call to clock.sleepFor(). That's because the script will run in a new thread, which is not the agent thread; and clock.sleepFor() is illegal outside the agent thread. The usual strategy is to comment out the clock.sleepFor() line, test the script with the Run button, and then, when you're done testing, uncomment the clock.sleepFor() line, and press the Compile button. Your agent is now live.

It's a good idea to supply a fairly large clock.sleepFor() value at first, so that there is time for you to stop the agent between calls to it if something goes wrong. I once wrote an agent into which I had put a call to dialog.notify() for debugging purposes. I then made the mistake of pressing the Compile button, and discovered that it was not easy to stop the agent; the dialog would appear, I would click its OK button, and the dialog would immediately appear again! I had a devil of a time getting things to pause. I would have had time to stop the agent if I had said clock.sleepFor(15) within the script.

How do you stop an agent? One way is to have the system.agents table ready to hand; selecting and cutting your agent script stops it from running. Another option is to select all the lines of your agent's script, turn them to a comment, and press the Compile button. Finally, choosing Disable Agents from the Main menu stops all agents; its name then changes to Enable Agents, and you can choose it again to start the agents up once more. This menu item relies on a call to frontier.enableAgents().

Agent Messages

A question arises as to how agents are to share the message area of the Main Window. If five agents are running and they all include calls to msg(), is the Main Window going to be bombarded with a constant stream of messages, none of which will remain long enough to be legible? And what about regular scripts that call msg()?

The answer is as follows. At the left end of the Main Window is a popup menu. Choosing from this popup does two things. First, it actually calls the chosen agent, whether it was time to call it automatically or not. Second, it turns control of the Main Window's message area over to the chosen agent. Only one agent at a time can write to the Main Window with msg(), and it is the one most recently chosen with this popup.1

When a non-agent script calls msg() , agents stop writing to the message area of the Main Window. If a non-agent script calls msg("") with the empty string as its parameter, or the user clicks the top half of the Main Window, the Main Window goes back to displaying messages from the agent last chosen in the popup menu.

Choosing from the Main Window popup while holding the Option key opens the agent script's edit window - a handy shortcut.

UserLand Agents

Here are the agents supplied by UserLand, and what they do.

StatusMessage

This is the agent whose messages I usually like to have showing; it tells the number of threads and the amount of Frontier's free memory. It also has another job, a very important one: it calls modes.monitor() , which is responsible for managing modal menus (see Chapter 26, Menus and Suites).

WebBrowserAgent

Watches to see whether Microsoft Internet Explorer or Netscape Navigator is active, so that one can drive the browser by calling suites.webBrowser verbs, regardless of which browser it is. It also reconciles the shared menus belonging the two browsers (see Chapter 26).

Scheduler Monitor

Installed by the scheduler suite to check once a minute whether any tasks are scheduled to be run; see Chapter 37, Scheduler.

MinutesSinceShip

Provides a harmless bit of trivia.

FrontierPath

Isn't really an agent at all, in a sense. When you choose it in the popup menu, it displays the pathname of Frontier on your disk, and then calls clock.sleepFor(infinity), which is as good as saying it is never to be called automatically. This is a useful trick: you can list in the agents popup menu, just because it is a convenient location, a utility without agent-like functionality.

Hooks

A hook is an internal Frontier mechanism that calls a script in response to a specific high-level event. Hooks are managed by associating particular areas in the database with particular events: scripts located in one of these areas are called when the corresponding events occur. Unless otherwise specified, such scripts take no parameters.

Open the database

As a database is opened,2 all the scripts in its system.startup table are called. Startup scripts are triggered, each in its own thread, and before any agents are called.
If a suite has private values that must be initialized every time Frontier starts up, it will typically install a startup script, using its installSuite() to do so (see Chapter 26). For example, scheduler.installSuite() installs a startup script, system.startup.["Scheduler Startup"], which sets a global value, scheduler.startingUp, to true. This way, when the scheduler monitor agent is first called, it knows that it is being called for the first time, and can take certain actions accordingly. See Chapter 37.

Close the database

As a database is closed,3 all the scripts in its system.shutdown table are called. This happens after the closeWindow hook has run, and after the "Save changes?" dialog has appeared if it needs to. It is impossible now to stop the database from closing, but it is possible to save it again (using fileMenu.save()); you'll need to do so if your shutdown script makes changes in the database.

Switch away from Frontier

If Frontier is frontmost, then when the user or a script brings another application to the front, all the scripts in the frontmost database's system.suspend table are called while Frontier is still frontmost.
A suspend script cannot prevent switching away from Frontier, but it can be used to bring Frontier back to the front again immediately. The following script, if placed in system.suspend, would effectively prevent the user or a script from being able to make any application frontmost other than Frontier:
while sys.frontmostapp() == "UserLand Frontier?"
    clock.waitsixtieths(1)
sys.bringapptofront("UserLand Frontier?")
dialog.alert("Don't leave me!")

Switch to Frontier

If Frontier is not frontmost, then when the user or a script brings Frontier to the front, all the scripts in the frontmost database's system.resume table are called once Frontier is frontmost.

Close a window

For the closeWindow hook, see Chapter 24, Windows.

Modifier-double-click text

When the user double-clicks text while holding the Control key, the Option key, or the Command key, the script at system.misc.control2click , system.misc.option2click, or system.misc.cmd2click, respectively, is called with a single parameter, the double-clicked text. Since more than one modifier key can be down simultaneously, an order of precedence is defined: Control key, Command key, Option key (that is, if the Control key is down, system.misc.control2click() is called regardless of what other modifiers are down, and so on).

There are already scripts at system.misc.control2click and system.misc. cmd2click. The former switches to the double-clicked verb's DocServer documentation (or, if the Option key is also down, inserts the parameter information from DocServer into the script or outline where the verb was double-clicked). The latter jumps to the database object whose name was double-clicked (additionally closing the frontmost window if the Option key is also down). See Chapter 13, Running and Debugging Scripts.

Hooks as Interrupts

A hook script may interrupt a script in progress; the script in progress cannot proceed until the hook script has finished executing. And you can't be exactly certain as to just when the interruption will take place.

For instance, if you have a script that says:

sys.bringapptofront("finder")
clock.waitseconds(5)
sys.bringapptofront("UserLand Frontier?")

and a hook script in system.suspend that says:

dialog.alert("Hold everything.")

then running the first script will switch away from Frontier but won't switch back; the first line has triggered the hook script, which is now stopped, waiting for a response to the dialog, and the first script has been interrupted, so it too is stopped.

Similarly, if you have a script that says:

sys.bringapptofront("finder")
clock.waitseconds(5)
sys.bringapptofront('LAND')
for x = 1 to 10
    msg(x)

and a hook script in system.resume that says:

dialog.alert("Welcome back.")

then if you run the first script, it's hard to know how far through its counting x will actually get before the counting is interrupted by the dialog.

User -Based Pseudo-Hooks

When a database is opened, a call is made to (if it exists) people.[user.ini-tials].customStartup(): it isn't a hook, properly speaking; it is triggered by the "startup" hook, though, because the startup hook calls the scripts in system.startup, and one of these is system.startup.startupScript(), which calls user.login(), which calls people.[user.initials].custom Startup().

When you choose Backup from the Main menu, then after the backup has been performed, a call is made to people.[user.initials].customBackup() if it exists. This isn't a hook at all; it's part of the functionality of the menu item script for Backup.

These are "pseudo-hooks" in the sense that they aren't the result of high-level events; they're just scripts called by other scripts in the usual way. They are worthy to be considered along with real hooks, though, because their purpose is similar: they allow custom automatic actions to be triggered. You could use the same technique to introduce your own pseudo-hooks.

Because these pseudo-hooks live in the people.[user.initials] table, they may be thought of as associated with a particular user rather than the database as a whole. Frontier presently has a simple system of differentiation between users - a call to user.switchUser() brings up a dialog to let the user enter a new user name, organization, and initials. A different people.[user.initials] table is then in force.


1. There is, as far as I know, no way to script an action equivalent to choosing from the popup.

2. From the File menu, or with filemenu.open(); opening a database "invisibly" with db.open() does not trigger this hook. See Chapter 30, Multiple Databases.

3. By closing its Main Window, or as a consequence of quitting Frontier.


TOP UP: Interface NEXT: Pictures

This is Frontier: The Definitive Guide by Matt Neuburg, unabridged and unaltered from the January 1998 printing.
It deals with the freeware Frontier 4.2.3; for commercial Frontier 8, look here, and for the inexpensive Radio UserLand, look here. See my Web site for information.
Those wishing an offline or printed copy may purchase the book, which is still in print.
Copyright 1998 O'Reilly & Associates, Inc. ALL RIGHTS RESERVED. Reprinted with permission.