Scripting Events

 

Andrew Clinick
Microsoft Corporation

April 9, 2001

Download Scriptevents.exe.

Scripting events. It should be easy enough. After all, many applications, both from Microsoft and from third parties, allow you to write script code that responds to events fired from objects provided by the application. This is a great way to script, since the application calls you at the appropriate time, and all you (the developer) have to know is to respond by writing the event-handler code. That's the theory at least. In reality, events are handled and implemented in any number of ways between applications, and it can quickly become confusing as to what is the best way to script events.

I thought I'd take this opportunity to try to clear up some of the confusion by exploring various options for writing event-handling scripts in popular applications—in particular, in Internet Explorer, Windows® Script Host, and Windows® Script Components.

How Do Events Work in A Script Engine?

Before digging into the details of how each application deals with events, let's take a look at how a script engine (the component that actually runs the script code in an application) deals with events. Essentially, two mechanisms are available to application writers to enable event handling: built–in or "automagic" support, and calling functions/subroutines.

"Automagic" Support

Both of the Microsoft® Windows® script engines, JScript® and Visual Basic® Scripting Edition, provide a built-in mechanism to hook up script to events through a naming convention. If you're a VBScript programmer, you are probably already familiar with the Object_Eventname naming convention for event handlers in Visual Basic® and in VBScript. If an application provides an object to the script engine, and puts the script engine into connected mode, then any subroutine with the correct naming convention will be called whenever the event is fired. For example, if the script has an object named "order," and it has an event "onNew," the subroutine would be called order_onNew(). The object must be available to the script engine when the script is compiled in order for the "automagic" hookup to work.

Hopefully this isn't news to VBScript programmers out there, but we often do get questions as to why JScript doesn't support this mechanism. JScript does support a built-in event-binding naming convention, but unfortunately it's not very well known. When we were building JScript 3, there was considerable debate within the script team as to what would be the best way to add this to JScript. Should it follow the VBScript notation, or do something new? It was felt that the Object_Eventname convention wasn't really in the spirit of JScript. Further, we recognized that there might already be code out there using the naming convention, so adding it in version 3 could result in serious compatibility issues.

The alternative proposed was Object:: Eventname, since JScript is related to C-like languages (and no, it really has nothing to do with Java, —the second most common question I get, after how to handle events in JScript!). To use the order object example again, the JScript event handler for theonNewevent would be order::onNew. To illustrate this, I've written a simple script host using Visual Basic and the Windows Script Control, so you can try the event handlers. The program provides an order object that has 3 events, onNew, onSave, and onDelete, and a simple text editor that allows you to write some script code.

Figure 1. The Event Test Bed application in action

Calling Functions/Subroutines

Whenever an application hosts a script engine (some examples of hosts would be Internet Explorer, IIS, and Windows Script Host), the application can access all the functions or subroutines that you have written in your script code. Not only can the host application see the functions, it can also call them. This ability to call code directly allows applications to offer an alternative mechanism for providing event binding to the script engine. Rather than use the built-in capability of responding to events, the host application can call the appropriate script function to handle any events fired by an object. This mechanism allows the host application to define a different syntax for event handlers, which, while powerful, can result in confusion as to how to respond to events in script. A host application can use a variety of naming convention/coding practices. For example, the order example could have the event handler for the onNew event implemented within the application, and it would call the orderOnNew function in the script.

Events in Internet Explorer

Internet Explorer is the leading source of confusion with regard to event-driven script, mainly because it has so many different ways of handling events. The obvious question is, why does it need so many ways of achieving the same result? The main reason is that events in HTML were never really standardized, and so IE had to support the many ways in which events could be hooked up to HTML, and deal with features unsupported in other browsers, such as ActiveX® controls. This is great for the end user, since nearly all content existing today will run in IE, but it does make the scriptwriter's life more complex.

Inline Script Handlers

The inline script handler is perhaps the most widely used mechanism for handling events in IE, since it is supported in both IE and Netscape. The basic principle is that you add the script inside the HTML element, and the browser does all the hook up for you. For example:

<span id="testinline" onclick='alert("inline handler - JScript")'>Click me inline - JScript</span>

The code provided in the onclick attribute will be called when the user clicks on the text enclosed in the span. This mechanism is great for small snippets of code, but it becomes cumbersome when you have a lot of script. This event mechanism works with both VBScript and JScript.

What happens behind the scenes is that Internet Explorer calls into the script engine with the script code and tells the engine to create an anonymous function (a function with no name). Those of you who know VBScript are probably wondering how it does this, since VBScript doesn't support anonymous functions. VBScript actually creates a subroutine called "anonymous" containing the script and returns a pointer to the function that is then hooked up to the event.

Function Pointers

Every function in JScript is a first-class object, which allows you to pass around pointers to the function and use them to call the function. This offers flexibility in hooking up event handlers, enabling you to use the same function to handle one or many events. To setup function-pointer events, just set the EventName property on the object to the pointer of the function. In JScript this is very simple to do. For example:

window.onclick=foo
function foo()
{
   alert('you clicked on the window')
}

In the first releases of VBScript, there was no way to use function pointers, since if you setwindow.onclicktofoo,VBScript would callfoorather than get a pointer. In VBScript 5.0 the GetRef method was introduced. This created a reference to the function, allowing you to set the event handler. For example:

set window.onclick=GetRef("foo")
function foo()
   alert('you clicked on the window')
end function

<Script> Block-Event Handlers

The event handlers used so far work well for HTML events, but don't provide a way for writing event handlers for custom events on ActiveX objects that you may have in your page. To provide this support, Internet Explorer introduced extensions to the <script> block to specify which object and which event the script is for. For example:

<span id="testscript">Click me script block handler - JScript</span>
<script language=JScript for=testscript event="onclick">
alert("script block handler - JScript")
</script>
<br>

Naming Conventions

The final Internet Explorer-specific event-hookup mechanism consists of naming your function to include the object and eventname. As long as your function is name objectname.eventname, IE will hook up the function automatically. For example:

<span id="testnaming">Click me naming - JScript</span>
<script language=JScript>

function testnaming.onclick()
{
   alert("script naming - JScript")
}
</script>

Note: This mechanism only works with JScript.

Automagic

In addition to all the IE-specific event handling mechanisms, you can also use the built-in "automagic" hook up provided by the script engines. The key to using this mechanism is understanding when the hook up occurs. If the objects or HTML elements are available when the page loads, then the script engine will do the event binding. If, however, the object or elements are added after page load (by settinginnerHTMLfor example) then the script engine won't hook up the events. The good thing about this mechanism is you can use it to respond to ActiveX Control events.

<span id="testscriptbuiltin">Click me Built In - JScript</span>
<script language=JScript>

function testscriptbuiltin::onclick()
{
alert("script naming - JScript")
}
</script>

<br>

<span id="testscriptbuiltin">Click me Built In - VBScript</span>
<script language=VBScript>

sub testscriptbuiltin_onclick()
   alert("script naming - VBScript")
end sub
</script>

View these event-handler examples in action.

Events in WSH

Windows Script Host has a much simpler event-hookup mechanism than Internet Explorer in that it offers only two ways to hook up events. (Hey, two is better than five!) Nevertheless, writing code in WSH that deals with events can still present a challenge, not the least of which is being able to ensure that your script is still running when the event fires. This is a problem because WSH scripts exit as soon as the last line of script is executed, which may be before the event you want to handle has been fired. To deal with this, I recommend using the WScript.Sleep method (see Sleep Method for more details) and create a loop that sleeps for a long time. Once the event has fired, use the WScript.Quit method to exit the script. (We realize this is a pain to do; a fix for this is on our list of enhancements for the next version of WSH after 5.6.) Once you've established a way to ensure that your event handler will be called, you need to make the decision between the two event-handling mechanisms.

WSH supports both "automagic" and a naming-convention-based event hookup. The naming-convention system, introduced with WSH 1.0, provides a way to respond to events from objects created using WScript.Createobject. This was necessary at the time, since there was no other way to create an external object in WSH. WScript.CreateObject is similar to the VBScript CreateObject, but adds a second argument that defines the naming convention for event-handler functions. For example:

Set myobject = WScript.CreateObject("someobjectwithevents","myobject_")

In this case, WSH will create an instance of the Someobjectwithevents object, and any functions that begin withmyobject_and end with the event name will be called when the event is fired. WSH achieves this by handling the event internally and calling the function that matches naming convention in the script engine. In VBScript this typically doesn't have much impact, since the "automagic" event hookup syntax isobject_eventname.In JScript, however, this is a little weird sinceobject::eventnamewon't work. Here's an example of CreateObject event binding:

set myorder2 = WScript.CreateObject("ordersystem.clsorder","myorder2_")
myorder2.neworder(1)

sub myorder2_onNew()
   Wscript.echo "new order received from myorder2"
end sub

The Windows Script team recognized that the WScript.CreateObject mechanism was confusing, and in WSH 2.0 introduced the<object>element in .wsf files. The<object>element works much like the<object>element in HTML, with one exception: By default, events fired by objects defined in<object>elements won't be hooked up to the script code in file. To allow event hookup, you need to set the Events attribute on the<object>element to True. For example:

<job>
<object progid="ordersystem.clsorder" id="myorder" events="true"/>
<script language="vbscript">
sub myorder_onNew()
   Wscript.echo "new order received myorder"
end sub
myorder.neworder(1)
wscript.sleep 5000
</script>
</job>

WSH includes a final feature to bind to the events of objects that are created after the script has been loaded. This is useful for objects that are created via CreateObject (in VBScript) or new ActiveXObject in JScript. To bind to the events of an already-created object, there is a ConnectObject method on WScript, and to disconnect, WScript.ConnectObject.

set myorder3 = createobject("ordersystem.clsorder")

wscript.connectobject myorder3,"myorder3_"

myorder3.neworder(1)
sub myorder3_onNew()
   Wscript.echo "new order received from myorder3"
end sub

Windows Script Components

Windows Script Components .wsc files are processed by the same component as .wsf files, so the<object>element provides the same features, including the Events attribute. The Wscript.CreateObject and Wscript.ConnectObject methods, however, are unique to WSH, so cannot be used outside of WSH.

Summary

Event-driven programming can make scripting applications considerably easier if you know what is available to you to bind to the events. As I've shown, it can be quite complicated to decide on the best mechanism for writing event handlers. It pays to look at the relative merits of each approach, and to select one that meets your needs without unduly complicating the task of scripting. As always, we look forward to hearing your feedback and ideas for making scripting easier. Please feel free to contact us at msscript@microsoft.com and to use the newsgroups on msnews.microsoft.com.