Sunday 11 February 2018

VBA - IE - CreateEvent and DispatchEvent to synthesise an event

Summary: Inject Javascript to create and dispatch an event in Internet Explorer using CreateEvent and DispatchEvent

IE is different from other browsers and so there sometimes is an 'IE way of doing things'. This is true when trying to synthesise an event, typically during web-scraping. To synthesise an event in IE requires calling CreateEvent and then calling DispatchEvent. The calling syntax is not obvious so here I lay down an example for reference.

The code injects javascript using the execScript method. Also a console stack trace feature is given.

TIP: It turns out that when webscraping and manipulating HTML input boxes etc. it is better for the manipulated HTML element to take the focus; this can obviate the need to synthesise events.

The VBA code

Option Explicit

'* Tools - References
'*      MSHTML      Microsoft HTML Object Library                   C:\Windows\SysWOW64\mshtml.tlb
'*      SHDocVw     Microsoft Internet Controls                     C:\Windows\SysWOW64\ieframe.dll
'*      Shell32     Microsoft Shell Controls And Automation         C:\Windows\SysWOW64\shell32.dll

Private Function ReacquireInternetExplorer(ByVal sMatch As String) As Object
    Dim oShell As Shell32.Shell: Set oShell = New Shell32.Shell
    Dim wins As Object: Set wins = oShell.Windows
    Dim winLoop As Variant
    For Each winLoop In oShell.Windows
        If "C:\Program Files (x86)\Internet Explorer\IEXPLORE.EXE" = winLoop.FullName Then

            Dim sFile2 As String
            sFile2 = "file:///" & VBA.Replace(sMatch, "\", "/")
            If StrComp(sFile2, winLoop.LocationURL, vbTextCompare) = 0 Then
                Set ReacquireInternetExplorer = winLoop.Application
                GoTo SingleExit
            End If
        End If
    Next
SingleExit:
End Function

Sub test()

    Dim objIE As InternetExplorer
    Set objIE = New InternetExplorer
    Dim oHtml As HTMLDocument
    Dim HTMLtags As IHTMLElementCollection


    Dim sUrl As String
    sUrl = "C:\Users\Simon\source\repos\WebApplication2\WebApplication2\HtmlPage1.html"

    objIE.Visible = True
    objIE.Navigate sUrl

    If StrComp(Left(sUrl, 3), "C:\") = 0 Then
        Stop '* give chance to clear the activex warning box for the local file
        Set objIE = ReacquireInternetExplorer(sUrl)
    End If
    Do Until objIE.readyState = READYSTATE_COMPLETE: DoEvents: Loop
    Set oHtml = objIE.Document

    Do
        '* wait for the input box to be ready
        Set HTMLtags = oHtml.getElementsByClassName("OrderForm_input-box_XkGmi")
        DoEvents
    Loop While HTMLtags.Length = 0

    Dim objWindow As MSHTML.HTMLWindow2
    Set objWindow = objIE.Document.parentWindow


    
    Const csJavaScriptConsoleTrace As String = "var divTotal = document.querySelector('div.OrderForm_total_6EL8d'); " & _
                                                 "divTotal.onchange = function() { console.trace(); }"
    
    objWindow.execScript csJavaScriptConsoleTrace


    '* next line sets the input box and raises an event, works on local file but not on GDAX
    
    Const csJavaScriptSynthesiseEvents As String = _
                "var inputBox = document.querySelector('div.OrderForm_input-box_XkGmi input'); " & _
                "inputBox.value = 100; " & _
                "if (document.createEvent) { " & _
                "  var event2 = document.createEvent('HTMLEvents'); " & _
                "  event2.initEvent('input', false, false); " & _
                "  event2.eventName = 'input'; inputBox.dispatchEvent(event2); " & _
                "}"
    objWindow.execScript csJavaScriptSynthesiseEvents


    'get the Total(LTC) to cross check
    Do
        '* wait for the order total div to be ready
        Set HTMLtags = oHtml.getElementsByClassName("OrderForm_total_6EL8d")
        DoEvents
    Loop While HTMLtags.Length = 0

    Dim divTotal As HTMLDivElement
    Set divTotal = oHtml.querySelector("div.OrderForm_total_6EL8d")
    Debug.Print divTotal.innerText & " Total(LTC)"

    Stop

End Sub

The HTML page

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title></title>
    </head>
    <body>
        <input id="Button1" type="button" value="Programmatically write textbox value" onclick="TestAlert()" />

        <form class="OrderForm_form_25r0u">
            <ul class="OrderForm_trade-type_2QyK4">
                <li class="OrderForm_trade-type-tab_uWGMp OrderForm_active_Di-9p">MARKET</li>
                <li class="OrderForm_trade-type-tab_uWGMp">LIMIT</li>
                <li class="OrderForm_trade-type-tab_uWGMp">STOP</li>
            </ul>
            <ul class="OrderForm_toggle_120Ka">
                <li class="OrderForm_toggle-tab_bZZnC OrderForm_buy_38n5g OrderForm_active_Di-9p">BUY</li>
                <li class="OrderForm_toggle-tab_bZZnC OrderForm_sell_3vYRQ">SELL</li>
            </ul>
            <div class="market-order">
                <div class="OrderForm_section_2Znad">
                    <div class="OrderForm_section-header_fwFDB">Amount</div>
                    <div class="OrderForm_input-box_XkGmi">
                        <input type="number" step="0.01" min="0" name="amount" 
           placeholder="0.00" value="" autocomplete="off" oninput="myOnInputHandler()">
                        <span>EUR</span>
                    </div>
                </div>
            </div>
            <div class="OrderForm_order-total_3Mkdz">
                <div>
                    <b>Total</b>
                    <span>(LTC)</span>
                    <b>≈</b>
                </div>
                <div class="OrderForm_total_6EL8d" >0.00000000</div>
            </div>
        </form>

        <script language="javascript">
            function myOnInputHandler() {
                print_call_stack();
                alert('you input something');
            }

            function print_call_stack() { console.trace(); }

            function print_call_stack2() {
                var stack = new Error().stack;
                console.log("PRINTING CALL STACK");
                console.log(stack);
            }


            function TestAlert() { setInputBox(document); }

            function setInputBox() {
                try {
                    var inputBox = document.querySelector('div.OrderForm_input-box_XkGmi input'); 
     inputBox.value = 100; 
     if (document.createEvent) { 
      var event2 = document.createEvent("HTMLEvents"); 
      event2.initEvent("input", true, true); 
      event2.eventName = "input"; inputBox.dispatchEvent(event2); 
     }

                    return ({ success: true });
                }
                catch (ex) {
                    return ({ exception: ex, myMsg: '#error in setInputBox!' });
                }
            }

        </script>
    </body>
    </html>

1 comment:

  1. Hello! This is super interesting and I have I think a similar situation I could handle with this.. could you help me out? Thanks a lot!

    ReplyDelete