How to end a repeat loop if the time it repeats can vary?

Hello, novice scripter here. Hopefully I can get myself properly educated. (Hopefully my question accurately sums up what I’m trying to ask)

Goal
From a website I’m working on, there is a table of user information that looks similar to this
[format]
Name Email UserID UserType
Bob Smith b.smith@test.com 123123 Admin
Sammy Jordan sjo@test.com 456456 User
Ash McKatchem a.mck@test.com 789789 Account Owner
[/format]
I have a handler that will scrape the user’s name from the table on the website and it will pop up a “choose from list” with a prompt asking “Which user would you like to select?”

My script


--Name Scraper
To NameScraperHandler(rowNumber)
 (handler scripts)
End NameScraperHandler

set userList to {}
set x to 1
repeat until x is 3
     set userName to NameScraperHandler(x)
     copy userName to end of userList
     set x to x + 1
end repeat
choose from list userList with prompt "Which user would you like to select?"

For the sake of demonstration, the NameScraperHandler would just get the user’s name from the table by going down the rows. NameScraperHandler(1) would return Bob Smith and so on.

The issue with this is that each account has a different number of members (with maximum of 50). That obviously makes “repeat until x is n” not work unless I can figure out how to automatically set n to the length of the table.

And thinking that the max number is 50, I tried using “repeat until x is 50” but the handler I’m using to scrape the name from the table breaks when it doesn’t register more names to scrape.

(The actual NameScraperHandler uses JavaScript and there is another cleanup handler that’ll trim the output of the html string that it returns. When there’s no value, it gives -2753 error)

I don’t think the issue is with my handlers because I should be able to do “try/on error” command or something else to break the repeat loop

I thought about making the repeat loop end until it gets the -2753 error, but I don’t think I can use error numbers like that lol

I’d DEEPLY appreciate any assistance and insight!

Cheers

  1. You need to use the do javascript command to pick a particular table username in a web page. 2) If you gave the URL of the webpage, I could be more specific with the assistance.

You shouldn’t specify to web page how many users are online, but it itself should give their number and list to your script. The sequence of the process is as follows:

  1. You get a list of users online from a webpage using the do javascript (“…”) command.
  2. Choose a user from the list using the command choose from list.
  3. Send a click to the selected username using again the do javascript (“…”) command.

Step 3) should be placed in a try block in case the user disconnects while you are digging. Processing on error of the try block should display the message “Too late! User is offline. Choose another or Cancel”

Hi KniazidisR! Thank you SO much for your input!

Unfortunately the webpage I’m working on is protected by private VPN as the user data is sensitive otherwise I would definitely have xD

Yes my NameScraperHandler does have “do JavaScript”. In fact, here’s the actual handler


to ScrapeByClass(theClass, num)
	tell application "Safari"
		set input to do JavaScript "document.getElementsByClassName('" & theClass & "')[" & num & "].innerHTML;" in document 1
	end tell
	return input
end ScrapeByClass

And the challenge I’m facing is a bit less intimidating than the scenario you gave xD (I don’t have the issue of people signing on/off in real time) Sorry if I wasn’t clear.

What I’m looking at is a table of members in one account (one account can have various amount of members up to 50). So repeat loop would use that ScrapeByClass handler and go down the rows of the table and grab each member’s name. My challenge here is that I don’t know how to automatically determine how many rows are in the table (aka how many members)

Currently I’ve tentatively resolve it by using another handler that’ll count the occurrence of a string. In the member table, each row has a “view” hyperlink at the end that allows us to go into the member’s detail. I used a similar “do JavaScript” handler, grabbed the entire table’s html code and have the handler count the occurrence of “view”.

While this method works I thought there’s better approach. Like perhaps I can tell the script to stop repeating when there is no more value to scrape but I’ve no idea how because I’m a noob :cry:

Regardless, I sincerely appreciate your help!

I did not check, but it seems to work:


set userList to {}
repeat with x from 1 to 50
	try
		set userName to NameScraperHandler(x)
		copy userName to end of userList
	on error
		exit repeat
	end try
end repeat
choose from list userList with prompt "All users: " & ((x - 1) as string) & return & return & "Which user would you like to select?"

There may be a better approach if the class name remains the same for a single column as you descend through its rows. If this is the case, you can let JavaScript enumerate all of the values in a single column and return is as an array, which will be given back to you as an AppleScript list:

to ScrapeByClass(theClass)
		tell application "Safari" to tell ¬
				the front document to ¬
				return do JavaScript ¬
				"Array.from(
						document.getElementsByClassName('" & theClass & "'),
						x => x.innerText
				);"
end ScrapeByClass

Alternatively, should that syntax not work for whatever reason, this will perform the same function:

to ScrapeByClass(theClass)
		tell application "Safari" to tell the front document to return ¬
				do JavaScript "[...document.getElementsByClassName('" & theClass & "')]
										   .map( x => x.innerText );"
end ScrapeByClass(theClass)

This would negate the need for any repeat loops, and when you call this handler for each column, then you will get back an AppleScript list for each column of data.

If the class names aren’t the same—or share a common substring (that you could enumerate with querySelectorAll() instead of getElementsByClassName())—then this won’t work, and you can disregard my suggestion.

KniazidisR
THIS!!! UGH It’s so weird because I had scripted something similar and it didn’t work… then I first copied your method, it also didn’t work but after retyped it, voila! That’s it!! As simple as I thought it should be but just needed someone experienced to confirm and do the magic touch lol

MUCH MUCH appreciated!! :cool:

HOLY CRAP, I know in my learning process I’m gonna have a lot of moments like this. I’m so novice I have ABSOLUTELY no clue what Do JavaScript is capable of. I feel like JavaScript could do EVERY and ANYthing given I know how.

I just used your script and obtained a whole row of user info without having to trim it. The only program with this method, again it may just be a noob-arse issue, what is returned is all of the info in one string. So the result I get is

Which I’m not entirely sure how I can use that information individually.

PS. I failed to mention in my original post (as I didn’t get that far) my goal is to automate the manual process where we have to go into each account, find the user we need, and put all that info in an easily readable template.

I’ve managed to achieve that with everyone’s help but I know my script can certainly be shorter if I know how to properly manipulate a string of info.

Thank you x 1000 once again CK!

In AppleScript, you can use paragraphs of … to split a block of text into lines:

paragraphs of "Bob Smith
b.smith@test.com
123123
Admin
" --> {"Bob Smith", "b.smith@test.com", "123123", "Admin", ""}