The Container Object

All Scriptlets runs in an HTML page by the means of an embedded container. This container recreates for the Scriptlet the same habitat as IE 4, and makes available the ambient properties listed earlier. In addition, it provides the following methods:

Method Description
bubbleEvent Passes the current event down to the host environment, whether an HTML page or a Visual Basic form.
raiseEvent Fires a custom event for the Scriptlet. Each event is identified by name but routed through the single onScriptletEvent of the container.
setContextMenu Allows us to associate a pop-up menu with the Scriptlet. The menu will appear after a right-click on the Scriptlet's area of the page.

A Scriptlet receives notification of all the standard Dynamic HTML events for the window and document objects, and any other elements it contains. bubbleEvent is the method we use to pass (or bubble) any events we trap in the Scriptlet back up to the container. If we don't pass these events explicitly, event bubbling stops at the container and the host HTML page (or VB form., etc.) will never know that an event occurred.

If we want to handle an onclick event, for example, then bubble it back up to the container page, we could use the following code within our Scriptlet:

<script language="VBscript" 
  for="document" event="onclick">
  if (InScriptlet) And Not (window.external.frozen) Then
    window.external.bubbleEvent
  end if 
</script>

Aside from just checking to see if the Scriplet page is being run as a Scriptlet (indicated by the InScriptlet variable) we also have to check to make sure that the hosting container is ready to receive events. This is done by making sure that the window.external.frozen property is false.

Once the event gets back up to the container page, we can handle it there. We do this using the container window's event object, and examine it to get information about the event. So code to handle a bubbled event might look like this:

<script language="jscript" for="Scriptlet1" event="onclick">
// inspect event object to see which button was clicked
if (window.event.button = 2) 
  alert ("Right-Button Clicked");
</script>

If the Scriptlet is hosted in a non-Web browser environment, such as a C++, Visual Basic, or Office97 application, we have to use the event object that hangs of the Scriptlet control, rather than the integral window object's event object. We'll look at hosting Scriptlets in these kinds of applications towards the end of this chapter.

Handling Custom Events

The raiseEvent method fires custom events, such as the onPainting event we discussed earlier. Its prototype is

RaiseEvent(name, data)

The following is a typical call to the raiseEvent method:

window.external.raiseEvent "onPainting", window.document

The first argument of raiseEvent is the name of the event we want to raise. The second is the data we want to associate with the event, and notify to the receiver. Whatever name we assign to the event, it will always be detected and handled in the container through the onScriptletEvent – which is the counterpart of raiseEvent. Because of this, there is no need to declare our events in a Scriptlet, as they all arrive through the built-in onScriptletEvent event.

When the client is notified of a custom event (i.e. one that is not a standard DHTML event), it can distinguish which of the Scriptlet's event this is with multiple if..then statements, or with a select..case or switch statement—based on the name argument. For example:

<script language="jscript" for="Scriptlet1" event="onscriptletevent(eventname, eventdata)">
if(eventname == "onPainting") {
    alert("Start painting");
}
else {
  if(eventname == "onEndPainting") {
    alert("End painting");
  }
}
</script>

The Scriptlet would raise these events to the container using:

window.external.raiseEvent "onPainting", window.document
window.external.raiseEvent "onEndPainting", window.document

The window.document parameter simply represents the data we want to pass to the container's event code. This, of course, will change according to the actual requirements of your code. As previously discussed, the window.external.frozen property should be checked before firing events.

All the events a Scriptlet raises are perceived by the container as occurrences of the same event, onScriptletEvent, with different parameters. This also means that we can define events at any time, and qualify them with a string.

Adding a Context Menu

The Scriptlet container object also allows us to create and assign a pop-up context menu to a Scriptlet. To do this we must use either VBScript or JavaScript, as these are the only script languages guaranteed to create arrays compatible with the expectations of the setContextMenu method.

To create a context menu that shows n items, we define an array of 2*n elements. For each pair of elements in the array, the first item is the caption that will be shown on the menu, while the second is the name of the function that will executed when the user selects that item on the menu (notice that these procedures cannot take parameters). If we want an item separator, just add a couple of empty items. Finally, we pass the array to the setContextMenu method of the window.external object:

<SCRIPT language="VBScript" for="window" event="onload">
   dim menuItems(6)     
   
   menuItems(0) = "&One"  
   menuItems(1) = "One"

   menuItems(2) = "&Two"   
   menuItems(3) = "Two"

   menuItems(4) = "&Three"   
   menuItems(5) = "Three"

   window.external.setContextMenu(menuItems)
</SCRIPT>

Once we have assigned the context menu to the external object, we're done. We have to do nothing more to set up the menu, and have no need to investigate which button the user presses. The container takes care of handling the right-click event, and running the appropriate code, automatically. Here's the result:

Creating Dynamic Context Menus

The best place to create the context menu is the window.onLoad event, as we did in the previous example. If we use a different event, we have to wait for that event to occur before the context menu is initialized and displayed. The context menu can be changed in code during Scriptlet execution, but all the items displayed at any moment in time must occupy consecutive locations in the menu items array. The following example produces the same menu as in the previous example the first time it is clicked. From the second time onwards, the menu changes to include a separator and a fourth menu item:

<script language="VBscript">
dim menuItems(10)     
dim bFirstTime

Sub InitMyScriptlet
  if InScriptlet then
    window.external.selectableContent = mSelectable
    menuItems(0) = "&One"  
    menuItems(1) = "One"
    menuItems(2) = "&Two"   
    menuItems(3) = "Two"
    menuItems(4) = "&Three"   
    menuItems(5) = "Three"
    menuItems(6) = "xxx"     ' stub the next items
    menuItems(7) = ""        ' stub the next items
    window.external.setContextMenu(menuItems)
    bFirstTime = 1
  end if
End Sub

Sub document_onmousedown
  if bFirstTime = 0 then
    menuItems(6) = ""       ' separator
    menuItems(7) = ""       ' separator
    menuItems(8) = "&Four"
    menuItems(9) = "Four"
    window.external.setContextMenu(menuItems)
  else
    bFirstTime = 0
  end if    
End Sub

Sub One
  MsgBox "One"
End Sub

Sub Two
  MsgBox "Two"
End Sub

Sub Three
  MsgBox "Three"
End Sub

Sub Four
  MsgBox "Four"
End Sub
</script>

The context menu gets initialized first in window.onload event, then is updated in the document_onmousedown event. In any case, before the menu is displayed, our Scriptlet will receive an onmousedown event, so we have the opportunity to initialize it, or make changes to it, there. Here's the result—you can run or download this page, MenuDemo.htm, from our Web site at http://rapid.wrox.co.uk/books/0707:

If you aren't using different arrays for different menus, you probably declared an oversized array to make room for new items. In this case, we recommend you use:

menuItems(6) = "xxx"     ' stub the next items
menuItems(7) = ""        ' stub the next items

to suppress the unnecessary items, by assigning a non-empty caption and an empty or non-existing procedure. Otherwise you risk being flooded with item separators from the multiple empty strings.