Skip to main content
Version: 23.3

Handlers

Handlers are message listeners within a script. An object has a script, which has handlers. The object must have access to a handler for a particular message in order to receive that message and act on it. For more on messages, see Messages.

An object’s behaviors are defined by the way it handles different messages that may be sent to it. When an object receives a message, it will respond and perform a scripted action, but only if it has a script with a handler for that particular message. All scripts consist of one or more handlers for the various messages that the object is interested in handling (including the initial handler of a script, which handles a message by the same name as the script). If an object receives a message for which it doesn’t have a matching handler, it ignores that message.

A SenseTalk script is made up of handlers. A handler is a part of a script that defines what the script will do when a particular message is sent to it. There are three primary types of handlers: command handlers (sometimes called on handlers), function handlers, and generic handlers (also known as to handlers). There are also two special types of handlers, getProp and setProp handlers, discussed in Properties.

Types of Handlers

Command, Function, and Generic Handlers

There are three primary types of message handlers: generic, command, and function handlers. Generic handlers begin with to handle (or simply to) and can handle both command and function messages. For simplicity, always use generic handlers unless there is a special need to handle command and function messages differently. Command handlers begin with the word on and handle messages sent by commands. A command handler typically performs a series of actions. Function handlers, which begin with the word function, handle function call messages, and return a value.

It should be noted that a command handler may also return a value, and a function handler may perform actions in addition to returning a value. The only real difference between the handler types is the kind of messages they will handle: a command handler will never be run as a result of a function message, and a function handler will never be called by a command message.

note

If a script contains both a generic handler (to) and a specific handler (on or function) for the same message name, the specific handler always receives priority and receives the message. The to handler still receives messages of the other type. If a script contains all three types of handlers for a particular message, the on handler receives command messages by that name, the function handler receives function messages, and the to handler is never called.

The following is a very simple handler for a greetTheUser message:

to greetTheUser
put "Welcome to SenseTalk. Happy scripting!"
end greetTheUser

To, To Handle Keyword

Behavior: The to or to handle keyword declares a generic handler that can receive both command and function messages. If the handler needs to take different actions depending on how it was called, the messageType function can be used to find out whether the handler was called as a command or as a function.

When the script receives either a command message or a function message matching messageName, the statements of this handler are executed. The incoming parameter values passed to the handler is assigned to the corresponding parameter names (paramName1 , paramName2, paramNameN) declared following the messageName.

Syntax:
to {handle} messageName {{with | of | given} {a | an | the} paramName1 {, paramName2 ...} }
    statements
end [messageName | handler | to {handle}]

Example:

to handle increaseSize given amount
if amount is empty then add 1 to my size else add amount to my size
end increaseSize

On Keyword

Behavior: The on keyword is used to declare a command handler, and an end keyword ends it.

When an object receives a command message matching handlerName, the statements of that handler are executed. The incoming parameter values passed to the handler are assigned to the corresponding parameter names (paramName1 , paramName2, paramNameN) declared following the handlerName.

Syntax:
on handlerName {{with | of | given} {a | an | the} paramName1 {, paramName2 ...} }
    statements
end [handlerName | handler | on]

The example below defines an addToTotal command handler. If a parameter is passed with the message, its value is available in the newAmount variable, otherwise that variable is empty.

Example:

on addToTotal newAmount
add newAmount to global total -- Store total in a global variable
end addToTotal

Function Keyword

Behavior: The function keyword declares a function handler. When the script receives a function message matching functionName, the statements of this handler will be executed. The incoming parameter values passed to the handler will be assigned to the corresponding parameter names (paramName1 , paramName2, paramNameN) declared following the functionName. The returnValue will be passed back to the calling script as the value of the function call.

Syntax:
function handlerName {{with | of | given} {a | an | the} paramName1 {, paramName2 ...} }
    statements
    return returnValue
end [handlerName | handler | function]

Example:

function getTotal
return global total
end getTotal

Initial Handlers

In addition to explicitly-declared handlers beginning with the to, on, and function keywords, a script has an initial handler, consisting of all of the statements from the beginning of the script (after skipping any properties declarations at the very beginning of the script) to the first explicit handler or properties declaration. The initial handler type is actually the most common; any script that has no explicit handlers is just an initial handler. What we describe as a "script" is actually an initial handler.

The initial handler is treated as a generic to handle type of handler, which will handle any messages with the same name as the script (less any extension and illegal characters), and can respond to both command and function messages. In the case of an unnamed object (one that is created as a property list in a script, rather than loaded from a script file), the initial handler can be called using the run command or function. For objects other than script files, the initial handler is assigned the name <initialHandler>.

The only way to declare parameters in an initial handler is to use a params declaration.

note

If a script actually contains a named handler by the same name as the script (such as “to handle scriptFileName”), that handler takes precedence, and the initial lines of the script prior to the first named handler are ignored.

note

When a message is sent to a script object that resides on disk, SenseTalk reads that script file and caches the script in memory. If the object then receives another message, SenseTalk can check very quickly whether it has a handler for that message. In some (fairly rare) situations it may be desirable to have SenseTalk check for updates to the script during a run. In these situations, you can set the watchForScriptChanges global property to true (the default setting is false). This will cause SenseTalk to check the file for updates each time the object receives a message. If the file has been updated, it will be read again, and its new handlers will be used. The executing version of a handler is not changed while it runs.

Receiving Passed Parameters

When a handler expects to receive parameters passed to it by the calling script, it can list variables for those parameters following the message name (or in a params declaration on the first line within the handler—particularly useful in an initial handler).

Parameters can be passed either sequentially, or using key:value pairs that can be received as named parameters. Sequential parameters act like you might expect - they pass parameters in order, and the called handler must be set up to receive these values in that order. Named parameters use key:value pairs that allow you to refer to the property value by its name within the handler, and so access the information that way. For more information on how different parameters can be passed, see Parameters and Results.

Example:

Here is an example of a basic function handler that will calculate the quotient of two numbers, after first checking for a zero divisor. The words dividend and divisor in this example are treated like local variables within the quotient handler, with initial values already assigned to them from the first two sequential parameters passed by the calling function.

function quotient of dividend, divisor
if divisor is zero then return zero
else return dividend/ divisor
end quotient

Example: Receiving Sequential Parameters**

This example shows how three parameters passed sequentially are received by three corresponding parameter variables in the declaration of the called handler:

castSpell "sleep", 12 hours, "deep" --This is the calling command, passing three parameters sequentially

to castSpell spellName, duration, potency --The sequentially passed parameters are received in order and inserted into their corresponding parameter variables
//Do things to cast the spell:
Put duration and potency into Cauldron
Stir Cauldron -- A custom call that executes the spell
Log "Alakazam!: " & spellName -- A message confirming that the spell has been cast
end castSpell

For more information on the assignment of sequential parameters, see Sequential Parameter Assignment.

Example: Receiving Named Parameters**

This example shows how a single property list is passed followed by by name, so that the value of each key is inserted into the corresponding property variable in the declaration line of the handler:

castSpell {spellName:"sleep", potency:"deep", duration:15 minutes} by name --This is the calling command, passing a single property list by name

to castSpell spellName, duration, potency --The parameter variables set up here will now simply refer to the value of the corresponding property key. Order does not matter; "sleep" will be inserted into the spellName variable, "deep" into potency, and 15 minutes into duration.
//Do things to cast the spell:
Put duration and potency into Cauldron
Stir Cauldron -- A custom call that executes the spell
Log "Alakazam!: " & spellName -- A message confirming that the spell has been cast
end castSpell

For more information on the assignment of named parameters, see Named Parameter Assignment.

Accepting a Variable Number of Parameters

In SenseTalk, commands and functions can be called with any number of parameters, regardless of what a handler may be expecting. If fewer values are passed in by the calling script than the number of parameter declarations in a handler, the declared parameters for which there are no initial values will be set to empty.

If more values are passed to a handler than the number of named parameters that it declares, the additional values can be accessed from within the handler by using the param or parameterList functions. The number of parameters that were passed in can be obtained by the paramCount function. Here is an example that uses these functions to find the median (middle) value from among all of the parameter values that are passed to it, ignoring any values that are not numbers:

to handle medianNumber
set numList to be an empty list
repeat with n=1 to the paramCount -- Step through all parameters
if param(n) is a number then insert param(n) into numList
end repeat

sort the items of numList in numeric order
return the middle item of numList
end medianNumber

Another way to handle a variable number of parameters passed to a handler is to list one or more variable names after the handler name (or in a params declaration), and follow the last variable name with an ellipsis character or three dots (...). By doing this, that variable will be set to a list containing all of the additional parameters:

put quoteandjoin(",", Elizabeth, Aditi, Ricardo, Carrie, Eggbert)

to quoteAndJoin given joiner, namesToJoin... -- The joiner in this case is the comma passed as the first parameter. The ellipsis at the end allows the handler to accept any parameters beyond the first one to be passed as a list to that variable, called namesToJoin.
put namesToJoin -- Prints a list containing all of the properties passed after the first: ["Elizabeth","Aditi","Ricardo","Carrie","Eggbert"]
get namesToJoin joined by (quote & joiner & quote)
return quote & it & quote -- Prints: "Elizabeth","Aditi","Ricardo","Carrie","Eggbert"
end quoteAndJoin

Default Parameter Values

A handler can be called with any number of parameter values passed to it. Frequently, some of the expected (declared) parameters are considered optional, meaning that the call to the handler may or may not pass a value for those parameters. In these cases, it can be helpful to declare a default value other than empty for any of these values.

The default value only applies if no sequential or named parameters are received for that parameter declaration. When a handler is called, its declared parameter variables are given values in turn, beginning with the first one. If a sequential parameter value was given by the calling script for that variable, it is used. If not, but a named parameter value was given, that will be used. Because the parameter variables are assigned in order, it is possible to use an earlier variable's value in defining the default value of a later variable.

To declare a default value for a parameter, follow the parameter declaration with a colon (:) and the default value.

Example:

In this example, only c has a default value specified (Yes). Parameter variables a, b, and d will default to empty if no value is supplied by the calling script.

to handle GuestRegistration with a, b, c:Yes, d
--
end GuestRegistration

Example:

Here, the to reconnect handler accepts 3 parameters. If it is called without any parameters the server parameter variable will be assigned the value returned by calling the currentServer function. The attempts variable will be set to 3, and the disconnectFirst variable will be set to Yes.

to reconnect server: currentServer(), attempts:3, disconnectFirst: Yes
--
end reconnect

Expressions as Default Values

Default parameter values can be a simple value or any expression. The expression is only evaluated if no value is supplied for that parameter variable, by either a sequential parameter or a named parameter. For information on sequential and named parameters, see Parameters and Results.

Example:

In this example, no parameters are passed to the handler in the calling line of the script, so the default values are used for both parameter declarations.

Greet -- calls the Greet handler, passing no parameters.

to greet user:"Mysterious One", salutation: !"Greetings, [[user]]!" -- Both default values are used because no parameters were passed
put salutation -- Prints: "Greetings, Mysterious One!"
end greet

Advanced Message Handling

Handling Any (<any>) Messages

Each handler will handle messages with only one name. So, a to drive handler will only be called when a drive message is sent. On rare occasions, it may be helpful to create a handler that can receive messages with any name. This can be done by specifying <any> instead of a message name. This capability may be particularly useful in a getProp handler or in a script in the frontScripts.

There are a few special rules associated with these <any> handlers. If a script has a handler for a specific message, that handler will be called rather than the <any> handler — the <any> handler will only be called for messages that would otherwise not be handled by that script. Due to the unique ability of an <any> handler to handle any message that comes along, it is specially blocked by SenseTalk from calling itself. Otherwise it would quickly become a recursive nightmare as each command in an on <any> handler would cause it to call itself.

The use of <any> handlers should be approached with caution in order to avoid blocking messages unintentionally. The param(0) function can be used to obtain the name of the actual message sent, in order to select the appropriate action or to pass messages that the handler is not intended to deal with:

on <any>
if param(0) does not begin with "x_" then pass message
// Put code here to handle commands beginning with x_
end on <any>

Handling Undelivered Messages

When a message is sent for which no handler is found, rather than immediately raising an error, SenseTalk sends an undeliveredMessage message to the target of the original un-handled message. An object can implement an undeliveredMessage handler to try passing the original message to some other object which may be able to handle it (see the pass original message to ... command), or dealing with the problem in some other way. If the undeliveredMessage handler executes a pass undeliveredMessage command, the usual error will be raised.

A pass original message to object command may be used to try passing an undelivered message to some other object. If that object handles the message, that will be the end of it, and execution of the current handler will end. If the object does not handle the original message, execution will continue. In this way, an undeliveredMessage handler can attempt to deliver the original message to one or more other objects which may be able to handle it.

to handle undeliveredMessage
repeat with friend = each item in my friends
pass original message to friend
end repeat
pass undeliveredMessage -- Give up and let it fail
end undeliveredMessage

GetProp, SetProp Handlers

In addition to the standard command and function handlers, an object’s script may include two special types of handlers for working with the object’s properties: getProp handlers for providing the value of a property, and setProp handlers for receiving a new value for a property.

Whenever a property of an object is being read, a getProp message for that property is sent to the object. If it handles the message, the value it returns is used as the value of that property. When the value of a property is changed, a setProp message is sent to the object, with the new value as a parameter. If the object has a setProp handler for that message, it is called, otherwise the property is set directly.

For example, here is a handler that will supply the area property of an object, based on its length and width:

getProp area
return my length * my width
end area

An object may also want to control setting a property. Here, setting the area of an object will actually change its length:

setProp area newArea
set the length of me to newArea
my width
end area

If an object accesses one of its own properties from within a getProp or setProp handler for that property SenseTalk will access the property directly rather than calling the getProp/setProp handler recursively.

My Direct Property

Behavior: Enables an object to access its own properties without sending any function or getProp/setProp messages. This can be done using the special syntax my direct propName (or my direct property propName).

Syntax:
my direct {property} propName

Example:

getProp age -- My age in years
return (the date - my direct birthDate) div 365.25 days
end age
note

The direct keyword can only be used with my, and is, therefore, limited to an object accessing its own properties. Access to another object’s properties will always involve calling getProp or setProp.

HandlerNames Function

Behavior: Returns a list of the names of each of the handlers in an object's script. The order in which the handlers are listed is undefined.

Syntax:
{the} handlerNames of anObject
handlerNames( anObject )

Example:

put handlerNames of Account