ActiveX event handlers in an HTA using Javascript

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 Javascript, 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 - JavaScript runtime error: Object expected

Also, static event handling is an option in VBA/VB6, because the declarations can be marked Private. However, in Javascript, 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 Javascript 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.

events
activex
jscript
hta
activexobject
asked on Stack Overflow Jan 18, 2017 by Zev Spitz • edited May 23, 2017 by Community

2 Answers

1

The error appears to be because

  1. in Javascript 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 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, 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>
answered on Stack Overflow Jan 27, 2017 by Zev Spitz • edited Feb 28, 2017 by Zev Spitz
0

I could not make the object::event syntax work. So, I delegated the event handling to an auxiliary script, which follows.

caller.js file:

var WordApp = WScript.CreateObject("Word.Application", "wd_");
WordApp.Visible = true;

WScript.Sleep(20000);

function wd_Quit() { //'object_event' works with js inside wscript.exe
    WScript.Echo("word quit");
}

Then, call this script from within a HTA file

main.hta file:

<html>
<script LANGUAGE="JScript">
    var shell = new ActiveXObject("WScript.Shell");
    shell.Run("caller.js");
</script>
</html>

Both files must be on the same folder. Run main.hta and the code in caller.js will be called, without polluting the global namespace of the HTA.

This example is for testing purposes. There is a 20 seconds timeout for the script. After this time, it will quit and events won't be processed.

If it works, tell me so we can create a better way of communicating from the HTA (mshta.exe) to the JS (wscript.exe). There is a chance of using "GetObject" from the HTA to control the word application (I did it once, but with VBScript), while the events are processed by the JS file.

answered on Stack Overflow Jan 27, 2017 by Eduardo Po├žo

User contributions licensed under CC BY-SA 3.0