0

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

crichavin
  • 4,672
  • 10
  • 50
  • 95

2 Answers2

1

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

CobyC
  • 2,058
  • 1
  • 18
  • 24
  • Thanks again CobyC for putting me on the right path. I used a solution very similar (see updated question for detailed solution). It also helped me figure out how I need to interact with another page where javascript wasn't working by simulating key entry using the Populate Text Field on Webpage function instead. – crichavin Dec 08 '22 at 23:03
0

I don't have QB but I put together a quick html page with a script.

This is the html

    <!DOCTYPE html>
<html lang="en">
<head>    
</head>
<body>
    <div class="table-responsive">
        <table class="table">
            <thead>
                <tr>
                    <th>Column 1</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>Value <input type="text" id="text-1-input" onblur="txtOnblur();">1</td>
                </tr>
                <tr>
                    <td>Value <input type="text" id="text-2-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-2-input").show(true);
            $("#text-2-input").val('blur triggered!');
        }
    </script>
</body>
</html>

When I run the following script on the page with Power automate it triggers the onblur event on the textbox

function ExecuteScript() {
var $txt = $("input[id='text-1-input']");
      $txt[0].onblur();
}

In action:

enter image description here

When I try and call the code in a similar way as you do I only get the list of controls linked to the blur event.

enter image description here

I'm assuming it's jQuery being used in QB. I tend to stick to native JavaScript when it comes to PAD, more typing, but less abstraction.

CobyC
  • 2,058
  • 1
  • 18
  • 24
  • I appreciate your efforts in trying to solve this. I converted the script to plain js but still have the same issue. I feel like I need to better identify how the QBO code has bound to the listener...to better mimic the call, but I'm just guessing. JS is not my strong suit. – crichavin Dec 06 '22 at 20:00
  • No problem at all, is there a particular reason for using the JS? have a look at [this answer](https://stackoverflow.com/questions/74157819/im-trying-to-get-power-automate-desktop-to-select-specific-checkboxes-based-on/74681623#74681623) that might help if it is trying to iterate through textboxes, it might help in finding a solution. – CobyC Dec 06 '22 at 21:48
  • for each row, I need to read the value in one text box, copy it, then paste it in the other text box in that same row...then repeat for all rows. I also have some exclusions on rows based on the employee name, so actually I need to read the Name cell value first, then if the name is NOT one of a few supplied, perform the copy/paste. Wasn't sure how to do that using native flows in PAD, so did it in JS. Is this possible with just flow steps? – crichavin Dec 07 '22 at 17:39
  • It should be, can you add an image to your question that shows what the screen looks like? Blank/block out any personal info. If there is a way to identify the cells with an html selector you should be able to do it all in flows. it will be a combination of loops, Get details of element, Populate text fields, variables. The UI elements are mapped by their html selectors, and you can replace those fully or partially with variables, more or less like the answer in the link from my previous comment. 1 UI element can be manipulated to represent any other just by changing the html selector of it. – CobyC Dec 07 '22 at 22:05
  • I added an image. Hi level logic is: Foreach Row, if Employee NOT Jane Doe, copy Regular Hours to OT Hours. (really it is a column further over called Bonus, but it made the image a lot wider...pretend the OT HRS in the image says BONUS. You can see the selectors in my original JQuery code in the question....within each row, their are inputs with an attribute `wageitemit` and it's value is what identifies it as Hourly Pay vs Bonus. Which type of functions would I need? One to grab the rows, then in loop read HOURLY_PAY input and paste into BONUS input. – crichavin Dec 07 '22 at 23:13
  • If you look at my second answer that might guide you in the right direction, instead of looping through the columns, you can loop through the rows. If the columns are always the same position you can hard code the selector and find the position with the CSS selector 'nth-child(n)' to select the correct input box – CobyC Dec 08 '22 at 21:34