ActiveX event handlers in an HTA using JScript
Asked Answered
R

1

1

In C# I can write event handlers as follows:

var wdApp = new Microsoft.Office.Interop.Word.Application();
wdApp.DocumentBeforeSave += (Document doc, ref bool saveAsUI, ref bool cancel) => {
   //do stuff here
};

In VBA/VB6, I can use static event handling:

Dim WithEvents wdApp As Word.Application

Private Sub wdApp_DocumentBeforeSave(ByVal Doc As Document, SaveAsUI As Boolean, Cancel As Boolean)
    'do stuff here
End Sub

I would prefer to use dynamic event handling. However, in JScript, even when using static event handling with the syntax described here:

var wdApp = new ActiveXObject('Word.Application');
wdApp.Visible = true;

function wdApp::Quit() {
    window.alert('Quit');
};

it fails:

0x800a138f - JScript runtime error: Object expected

Also, static event handling is an option in VBA/VB6, because the declarations can be marked Private. However, in JScript, both the variable and the handler have to be declared in the global scope.

Two questions:

  1. How can I handle events of Automation-created objects with JScript in an HTA environment? (Note: I know that it is possible in WSH using a prefix passed to CreateObject, and a function named wdApp_Quit, but I am looking for an HTA solution.)

  2. How can I do this without polluting the global scope?


There is an older question here.

Rickie answered 18/1, 2017 at 6:54 Comment(9)
I've had a look but I can't get anything to work. The WSH solution you mention uses "ConnectObject" and I've tried to use that in an HTA with the WScript.Shell but it's not working. There is an unanswered question simialr to yours on MSDN since 2009 so it looks like it may not be possible.Jahdai
Should be objectname::eventname. msdn.microsoft.com/en-us/library/ms974564.aspxCoady
Event connection, as far as I've seen, depends on the WScript object, which is not present in the mshta.exe host (which runs HTAs). However, the mshta can call a javascript object, with the wscript.exe host, and get access to the WScript object. Would such solution be OK for you, or do it seem too ugly?Schaffer
@EduardoPoço Event connection, as far as I've seen, depends on the WScript object -- As I noted in my (edited) question, the Microsoft extensions to Javascript allow defining a global function of the form varName::eventName() { } which will be called on the eventName event for the global-scoped varName object. This mechanism is available in both WScript and in HTAs. But I would like something that doesn't require global function declarations and global variables; similar to the C# mechanism -- wdApp doesn't have to be a global variable in order to attach / remove handlers.Rickie
@EduardoPoço Gordon describes trying a similar solution without success in this comment, but it would be worthwhile as long as I don't have to declare the function and variable in global scope.Rickie
This is getting too complicated for me but when you said that it could be running before the wdApp is created I remembered that I saw a connect object example on devguru that uses sleep before to wait half a second for the object to be created. I don't know if you can use settimeout but have a look at the code below.Jahdai
objWord = WScript.CreateObject("Word.Application") WScript.ConnectObject(objWord, "objWord_") objWord.visible = true blnWordVisible = true while (blnWordVisible) { WScript.Sleep(500) } function objWord_Quit () { blnWordVisible = false WScript.Echo("You quit Word.") } function objWord_DocumentChange () { WScript.Echo("You switched documents.") }Jahdai
It works! self.setTimeout("function wdApp::Quit() {window.alert('Quit');};", 500) I used your first bit of code and wrapped the wdApp::Quit() function in a settimeout and itworked without errors.Jahdai
@Jahdai RE: setTimeout see my answer.Rickie
R
4

The error appears to be because

  1. In Javascript/JScript function declarations are evaluated first, before the variable is initialized. It's as if the code was written thus:

     var wdApp;
     function wdApp::Quit() { ... }
     wdApp = new ActiveXObject('Word.Application');
    
  2. The Microsoft JScript parser interprets the specially-named declaration (with ::) as an instruction to attach the function as an event handler.

  3. But at the point of the declaration, the handler cannot be attached to the object referred to by wdApp, because wdApp at that point is still undefined. Hence, the error.

The solution is to force the function declaration to be evaluated after wdApp is initialized. This can be done in one of three ways1:

  1. Since the function declaration is hoisted only to within the function scope, wrap the function declaration in an IIFE:

     var wdApp = new ActiveXObject('Word.Application');
     (function() {
         function wdApp::Quit() {
             //do stuff here
         }
     })();
    
  2. Create the declaration using some sort of string -> code mechanism -- eval, setTimeout with a string, window.execScript, or new Function:

     var wdApp = new ActiveXObject('Word.Application');
     eval('function wdApp::Quit() { ... }`);
    
  3. Initialize the variable before the current SCRIPT block. Most of the examples in the Scripting Events article do this by setting the id property on some element before the SCRIPT block:

<object progid="ordersystem.clsorder" id="myorder" events="true"/>
<script language="jscript">
    function myorder::onNew() {
      WScript.Echo("new order received from myorder")
    }
//...

but this could also be done using multiple SCRIPT blocks:

    <SCRIPT>
        var wdApp = new ActiveXObject('Word.Application');
    </SCRIPT>
    <SCRIPT>
        function wdApp::Quit() {
            //do stuff here
        }
    </SCRIPT>

As far as polluting the global namespace, only the third variant requires wdApp to be in the global namespace; the other two variants can be wrapped in an IIFE.


1. Technically, there is a fourth way under IE and HTAs, but it involves non-standard HTML instead of non-standard Javascript; But it can only be used if the object is declared using the HTML OBJECT tag, not with new ActiveXObject( ... ).

<script for="wdApp" event="Quit">
    //do stuff here
</script>
Rickie answered 27/1, 2017 at 6:54 Comment(3)
so, :: only works with eval, or evalscript, is that it?Schaffer
@EduardoPoço Or any form of string evaluation -- setTimeout, or new Function.Rickie
@EduardoPoço It seems it's also possible to wrap in an IIFE. Updated my answer.Rickie

© 2022 - 2024 — McMap. All rights reserved.