Search code examples
javascriptjqueryautomationpower-automate-desktop

Which event to trigger, programmatically simulating user blur


I'm using Power Automate Desktop with an Execute Javascript flow to try to automate some user entry in a Quickbooks Online Payroll form.

When natively using the form, it seems there is an event triggered on blur to validate the numerical input, among other things.

Using the JS flow, updating the input values is not being recognized by the form as once I save it, it shows those inputs as empty.

So I thought I need to trigger the blur event to get the data to save. Here is my JS script:

function ExecuteScript() { 
   var $payrollTableRows = $("table").first().find("tbody > tr.enabled");
   var $regHoursInput;
   var decRegHours;
   var $bonusInput;
   var employeeName;
   
   console.log('Power Automate: Rows Found: ' + $payrollTableRows.length);
   
   $payrollTableRows.each(function(){
   
        employeeName = $(this).find("td:eq(1)").find("a").text();
        
            $regHoursInput = $(this).find("input[wageitemid='HOURLY_PAY']");
            if($regHoursInput){
                    decRegHours = Number($regHoursInput .val());
                    
                    $bonusInput = $(this).find("input[wageitemid='BONUS']");
                    $bonusInput.focus();
    
                    if($bonusInput){
                        $bonusInput.val(decRegHours);
                        $bonusInput.trigger('blur');
                    } 
                } 
    });
}

Here is the script that gets executed on focus and blur on the QB Payroll page. enter image description here

Why does the script initiated triggers not fire this code?

UPDATE 1: Adding image of page: payroll page

UPDATE 2: Posting the PAD flow I used. Also got a good overview of this from this video. And how to use Loop and Loop Index from this article.

My Flow: Power Automate Desktop Flow for updating HTML table


Solution

  • To do something similar without relying on the JavaScript you could use a variable and loops.

    Html used for this flow:

    <!DOCTYPE html>
    <html lang="en">
    <head>    
    </head>
    <body>
        <div class="table-responsive">
            <table class="table">
                <thead>
                    <tr>
                        <th>Column A</th>
                        <th>Column B</th>
                        <th>Column C</th>
                        <th>Column D</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>A <input type="text" id="text-1-input" value="One"></td>
                        <td>B <input type="text" id="text-2-input" value="Two"></td>
                        <td>C <input type="text" id="text-3-input" value="Three"></td>
                        <td>D <input type="text" id="text-4-input" value="Four"onblur="txtOnblur();"></td>
                    </tr>
                    <tr>
                        <td><input type="text" id="text-5-input" ></td>
                        <td><input type="text" id="text-6-input" ></td>
                        <td><input type="text" id="text-7-input" ></td>
                        <td><input type="text" id="text-8-input" hidden></td>
                    </tr>
                    
                </tbody>
            </table>
        </div>    
        <script src="https://code.jquery.com/jquery-3.6.1.slim.min.js"></script>
        <script>
            function txtOnblur(){
                $("#text-8-input").show(true);
                $("#text-8-input").val('blur triggered!');
            }
        </script>
    </body>
    </html>
    

    I used a bit of JavaScript to extract the number of columns and rows in the table, this could have been done with the flow function 'Extract data from web page', but I find the JavaScript a bit faster/easier.

    function ExecuteScript() {
    var table = document.querySelector('body > div > table');
    var colCount = table.children[0].children[0].children.length;
    var rowCount = table.children[1].children.length;
    return `${colCount} ${rowCount}`
    }
    

    Declare one UI element of the first input box. You can reuse this element by replacing/changing the selector properties to use a variable.

    enter image description here

    enter image description here

    In the flow assign the value that will match the HTML selector for the particular control.

    enter image description here

    and then use the same element wherever you want to change/extract a value (remember the variable now sets the UI element)

    enter image description here enter image description here enter image description here

    The full flow code (copy this and paste it to PAD to see the details) There will be errors on your side, but you will see the flow.

    WebAutomation.LaunchEdge.AttachToEdgeByUrl TabUrl: $'''http://localhost/stackoverAnswer/''' AttachTimeout: 5 BrowserInstance=> Browser
    WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() {
    var table = document.querySelector(\'body > div > table\');
    var colCount = table.children[0].children[0].children.length;
    var rowCount = table.children[1].children.length;
    return `${colCount} ${rowCount}`
    }''' Result=> cols_rows
    Text.SplitText.Split Text: cols_rows StandardDelimiter: Text.StandardDelimiter.Space DelimiterTimes: 1 Result=> ColsAndRows
    Text.ToNumber Text: ColsAndRows[0] Number=> numCols
    Text.ToNumber Text: ColsAndRows[1] Number=> numRows
    LOOP colIdx FROM 1 TO numCols STEP 1
        SET inputBoxVariable TO $'''text-%colIdx%-input'''
        WebAutomation.GetDetailsOfElement BrowserInstance: Browser Control: appmask['Web Page \'http://localhost/stackoverAnswer/\'']['Input text \'text-1-input\''] AttributeName: $'''Own Text''' AttributeValue=> inputBoxValue
        IF colIdx = 4 THEN
            WebAutomation.Focus.Focus BrowserInstance: Browser Control: appmask['Web Page \'http://localhost/stackoverAnswer/\'']['Input text \'text-1-input\''] WaitForPageToLoadTimeout: 60
            MouseAndKeyboard.SendKeys.FocusAndSendKeys TextToSend: $'''{Tab}''' DelayBetweenKeystrokes: 10 SendTextAsHardwareKeys: False
        END
        SET inputBoxVariable TO $'''text-%colIdx + 4%-input'''
        IF inputBoxValue <> $'''Three''' THEN
            WebAutomation.PopulateTextField.PopulateTextFieldUsePhysicalKeyboard BrowserInstance: Browser Control: appmask['Web Page \'http://localhost/stackoverAnswer/\'']['Input text \'text-1-input\''] Text: inputBoxValue Mode: WebAutomation.PopulateTextMode.Replace UnfocusAfterPopulate: False WaitForPageToLoadTimeout: 60
        ELSE
            WebAutomation.PopulateTextField.PopulateTextFieldUsePhysicalKeyboard BrowserInstance: Browser Control: appmask['Web Page \'http://localhost/stackoverAnswer/\'']['Input text \'text-1-input\''] Text: $'''Skip this one''' Mode: WebAutomation.PopulateTextMode.Replace UnfocusAfterPopulate: False WaitForPageToLoadTimeout: 60
        END
    END
    

    How it runs:

    enter image description here