How to call a function or command in another object?
The message path is one of the fundamental concepts of LiveCode and describes how messages flow between LiveCode scripts. Sometimes, however, a programmer may wish to break out of the current path. This lesson describes the basics of the message path and how to call handlers that are outside of the message path.
Understanding the Message Path
The message path defines how messages are handled within LiveCode. For example, which object should get the "mouseUp" message or how a button script communicates with a card script.
Each message, or handler call, moves up through the message path until an appropriate handler is found that can deal with the message. Consider a "mouseUp" message. The programmer may choose to handle this at varying levels: Possibly in the script of button that was clicked, maybe in script of the card in which the button sits, or even in a library stack. The "mouseUp" message keeps climbing up the message path until it finds a handler that can handle it.
The order in which messages climb this path is fixed, as shown in the diagram. The first rung of the ladder is front scripts. If no handler is found there, then we proceed onto object scripts. The next step if no handler is found there, is to look in the scripts of any groups the object belongs to. Then the card on which the object sits. Next background script, followed by the stack script of that card. Then the main stack script of any parent stacks. Then the scripts of any library stacks before checking any back scripts. If the messages was not handled at this point, then it is handled by the engine.
If an appropriate handler for the message is found, then the message stops climbing, unless the handler chooses to pass the message, in which case the climb continues. This allows the programmer to handle messages on multiple levels. For example, consider a status change message. We can handle this at an object level where we may change some text to reflect this new status. We can then pass this message on, where at card level we may choose to update the background color. We may choose to pass the message on again allowing us to carry out any reaction to the new status at stack level.
Messages can be introduced at any level of the message path. For example, "preOpenCard" is sent at card level. Message only climb up the path, so a handler call at group level is not able to find its way down to an object.
Escaping the Message Path
Consider the following situation. We are writing a handler in a stack script. From this handler we wish to call a command called "myCommand" that is defined in the script of a card called "myCard". We cannot call this command in the traditional manner, since the card script is below the stack script in the message path. We need to escape the message path. To do this, we can use the LiveCode command "call".
call "myCommand" of card "myCard"
The call command allows us to specify the command we wish to invoke and the object in the message path it is sent to. The command is then propagated through the message path as before until it is handled. The context in which the target command is executed is that of the parent handler which called it. For example, if the target command makes reference to a field named "myField", this field is evaluated in the context of the parent handler. This could be an issue if you are calling a command on a different card, if both the parent and target cards have fields named "myField". Here the field used is that of the parent card, even though the command itself resides on the target card.
If we wish the target command to execute in the context within which it is defined, we can use the LiveCode command "send". When a command is invoked via send, the context is temporarily switched to that of the command being called.
send "myCommand" to card "myCard"
The call and send commands only work with commands. These are handlers that do not return a value. If we wish to invoke a function (outside of the current message path), we need to use the LiveCode function "value". The "value" function takes two parameters, the name of the the function we wish to invoke and an object in the message path. So, if we want to invoke a function in a group called "myGroup" on a card called "myCard" and put the value returned into a variable, we can use the following code:
put value("myFunction()", group "myGroup" of card "myCard") into tResult
So far, we have only considered handlers which do not take any arguments. In order to invoke handlers with parameters via "call", "send" or "value", we simply alter the name of the target handler to include parameters we wish to pass. For example, if we wish to call our command with parameters "hello" and "world":
call "myCommand hello world" of card "myCard"
Functions are also handled in much the same way, remembering to surround any parameters with parentheses.
put value("myFunction(hello, world)", group "myGroup" of card "myCard") into tResult
We can also include variables as parameters by appending their contents to the handler name:
send "myCommand" && tParam1 & comma && tParam2 to card "myCard"
Beware that if any of our variables contain comma separated values, these values are evaluated as separate parameters. To circumvent this, we need to surround our variable contents in quotes:
send "myCommand" && quote & tCSV & quote to card "myCard"
Similarly, we should also take care to enclose all multiword strings in quotes. Since we are writing our variable contents to string before using "call", "send" or "value", arrays do not function as expected (unless we serialize them to string in the parent handler and de-serialize in the target handler). We can use the LiveCode command "dispatch" to work around this issue.
The "dispatch" command provides a cleaner alternative to "call", "send" and "value" handlers described above. The "dispatch" command works in much the same way but has a few subtle differences. First of all the basics. Consider our first example, calling "myCommand" on "myCard":
dispatch "myCommand" to card "myCard"
As with "send", "dispatch" temporarily switches context when the target handler is executing.
We can also use dispatch with functions, with the value returned by the function being found in "the result".
dispatch function "myFunction" to card "myCard" put the result into tResult
Parameters can also be passed using the "with" extension. Unlike the "call", "send" and "value" handlers, the parameters are sent directly to the target handler, rather than being extracted from the string used to call the handler. This negates the need to surround our parameters in quotes and also allows us to pass arrays:
dispatch "myCommand" to card "myCard" with "Hello World", tArray, tCSV
Unlike the "call", "send" and "value" handlers, which throw an error if they cannot find the target handler, "dispatch" reports the outcome of executing the target handler in the "it" variable. The value is one of the following:
• handled - the message was handled and not passed
• unhandled - no matching handlers were found
• passed - the message was handled but passed by all handlers