I am working on automating some data entry into an intranet web page. I have had success with this type of code in the past to click checkboxes, but have been unable to make it work on the plus signs that expand the rows. The below code does nothing, no error is prompted either, the code just runs it's course.
Here is my code:
Set div = IE.document.getElementsByTagName("div")
For Each i In div
'showExpand?
If i.id Like "iconShowA*" Then
If i.onclick = "showExpand(*)" Then
i.Click'Click plus sign
v = Replace(i.id, "iconShowA", "")
col.Add v 'store the numeric part
End If
End If
Next i
For Each v In col
Debug.Print v
Next v
The pertinent HTML lines are:
(What I'm trying to click, there can be a variable number of these with a different numerical identifier "iconShowA(x)")
<div id="iconShowA34" class="ui-icon ui-icon-plusthick" onclick="showExpand('34','34')" ;="" style="display: block;"></div>
(I also need to avoid clicking these)
<div id="iconShowA_N4" class="ui-icon ui-icon-plusthick" onclick="showExpandSub('4','4')" ;=""></div>
The code below was able to achieve desired results. I was unable to make the TagName convention work. This method uses getElementByID to navigate through the webpage. It seemed crucial that the full ID be used, so I used the Do While loop to iterate through numbers that were possible numbers used in the ID naming convention.
n = DateDiff("ww", firstDate, secondDate)'Determines number of plus' to click
v = 0 'Counter for plus click event
x = 6 ' starting value for numerical piece of Id
Do While n > v 'continue loop until all plus' have been clicked
Set div = IE.document.getElementById("iconShowA" & x) 'Id must be defined completely to click
If div Is Nothing Then 'tests if element exists
x = x + 7
Else
div.Click 'clicks an element that exists
v = v + 1
x = x + 7 'iterates id number by 7 as that is convention of website
End If
Loop
CSS selectors:
Assuming all the elements you want to click have showExpandSub and not showExpand then you can use a CSS selector to select these elements:
div[onclick^='showExpand(']
This says elements with div tag having attribute onclick with value starting with 'showExpand('.
CSS query:
VBA:
Use the querySelectorAll method of document to return a nodeList of all matching elements. You then loop the .Length to retrieve elements.
Dim aNodeList As Object, iNode As Long
Set aNodeList = ie.document.querySelectorAll("div[onclick^='showExpand(']")
For iNode = 0 To aNodeList.Length - 1
Debug.Print aNodeList.item(iNode).innerText
'Debug.Print aNodeList(iNode).innerText '<== Sometimes this syntax
Next iNode
Related
Ok, lets say i want to uppercase first character in words in selected range in Word document, except some expressions. I have a script which does similar thing, but i have issues with some expressions. In script i use Selection.Range.Case, but the problem is with URL addresses. Id like to leave everything in addresses smallcased, but Selection.Range.Case dissolves URL links to multiple strings, like: https, :, //, /, etc. end every first character of that URL string gets uppercased. The selected range of text is in numbered list, and URLs are last thing before next numbered item. Is there some solution where i can concatenate everything after http:// or https:// into one string right before next numbered item ? Thank you.
May try something like this if the selected range of text is only in numbered list
Sub test()
Dim Pg As Paragraph, Pos As Long, Rng As Range
For Each Pg In Selection.Paragraphs
If Not Pg.Range.ListFormat.List Is Nothing Then 'Process only bulleted list
PgTxt = Pg.Range.Text
'Debug.Print PgTxt
Pos = InStr(1, PgTxt, "http")
If Pos <> 1 Then 'bypass if http found at start of List item then no Case Change
If Pos > 1 Then Pos = Pos - 1 'http found some where within the List item
If Pos = 0 Then Pos = Len(PgTxt) ' if http not found in the list Item
Set Rng = ActiveDocument.Range(Pg.Range.Start, Pg.Range.Start + Pos)
Rng.Case = wdTitleWord
End If
End If
Next
End Sub
If IsArray(payCsv(pay_id)) = False Then
'create tempArray
lc = 0
Debug.Print "create array"
End If
If IsArray(payCsv(pay_id)) = True Then
Debug.Print " array exists, we should be able to get ubound"
lc = UBound(payCsv(0)) - LBound(payCsv(0))
l = l + 1
End If
I am using the above code to determine whether I can use Ubound on my 2D array (i.e. if the 2nd dimension is created, get length (ubound - lbound).
However, I am getting a compile error, even though condition 2 is false, it does not recognise that the code will not be relevant.
I am testing one array and the result is if I comment out "lc = UBound(payCsv(0)) - LBound(payCsv(0))" is "create array".
If I leave this line in there, I get the error "compile error - expected array"
Is this a bug in VBA?
If you want to access the UBound of the 2nd dimension of an array, the format goes like this:
UBound(payCSV, 2)
The MSDN page on this function may be helpful.
When you access payCSV(0) as you currently are, the code assumes that you want the 1st element within the 1st dimension of the payCSV array.
Perhaps you might want to try this?
If IsArray(payCsv(pay_id)) = False Then
'create tempArray
lc = 0
Debug.Print "create array"
Else
Debug.Print " array exists, we should be able to get ubound"
lc = UBound(payCsv, 2) - LBound(payCsv, 2)
l = l + 1
End If
I've written some code in vba in combination with selenium to parse data from different tables spreading across multiple pages. When I run my script I can see that it parses data from the first page and then keep clicking on next page button until there is no more button is available. However, I'm getting the data from first page and seeing the browser clicking on the next page button for nothing cause it doesn't fetch any data from other pages. I don't understand what I'm doing wrong here. Perhaps, the loop I have created has got something to do with it or I don't know. Thanks for taking a look into it. Here is the full code:
Sub Table_data()
Dim driver As New ChromeDriver
Dim tabl As Object, rdata As Object, cdata As Object
driver.Get "https://toolkit.financialexpress.net/santanderam"
driver.Wait 1000
For Each tabl In driver.FindElementsByXPath("//table[#class='fe-datatable']")
For Each rdata In tabl.FindElementsByXPath(".//tr")
For Each cdata In rdata.FindElementsByXPath(".//td")
y = y + 1
Cells(x + 1, y) = cdata.Text
Next cdata
x = x + 1
y = 0
Next rdata
driver.FindElementByLinkText("Next").Click
driver.Wait 1000
Next tabl
End Sub
Consider pressing the Next button outside of your loops. You should use it within another loop, and the loop should terminate when there is no more Next button to press (Run-time Error 7: NoSuchElementError)
Xpath //table[#class='fe-datatable'] returns Page numbers as well. You should be using the inner table which is //table[#class='fe-fund-tableBody'] by class name or if you seek by id //*[#id='docRows']. They will point to the same element.
You might have noticed there are 7 occurrences of the above mentioned element. Your code loops through the empty ones for each page. You can avoid this by looping through the first occurence only, like this: (//table[#class='fe-fund-tableBody'])[1] or (//*[#id='docRows'])[1].
I also would recommend to find a way to implicit/explicit wait instead of wait. If we don't go further to improve anything else, in the end your code should look something like this:
Sub Table_data()
Dim driver As New ChromeDriver
Dim tabl As Object, rdata As Object, cdata As Object
driver.Get "https://toolkit.financialexpress.net/santanderam"
driver.Wait 1000
Do
For Each tabl In driver.FindElementsByXPath("(//*[#id='docRows'])[1]") 'or "(//table[#class='fe-fund-tableBody'])[1]"
For Each rdata In tabl.FindElementsByXPath(".//tr")
For Each cdata In rdata.FindElementsByXPath(".//td")
y = y + 1
Cells(x + 1, y) = cdata.Text
Next cdata
x = x + 1
y = 0
Next rdata
Next tabl
On Error Resume Next
driver.FindElementByLinkText("Next").Click
driver.Wait 1000
Loop Until Err.Number = 7
End Sub
Personally I would change the way you are iterating the pages. It should be like this in pseudo code:
function element getNextButton(){
all_buttons = driver.findElementsByXpath("""//*[#id="Price_1_1"]/tfoot/tr/td/div/div/a""");
next_button = all_buttons[all_buttons.Size()-1];
return next_button;
}
main(){
next_button = getNextButton();
while true{
do something with your current table;
next_button.click();
wait(2); // wait some time till the page loads
next_button = getNextButton();
if next_button.text does not contains 'Next'{
break;
}
}
}
I have just tested it on Python:
from selenium import webdriver
import time
def get_next_button():
buttons = driver.find_elements_by_xpath("""//*[#id="Price_1_1"]/tfoot/tr/td/div/div/a""")
next_element_button = buttons[len(buttons)-1]
return next_element_button
chrome_path = r"chromedriver.exe"
driver = webdriver.Chrome(chrome_path)
driver.get("https://toolkit.financialexpress.net/santanderam")
time.sleep(5)
next_button =get_next_button()
while(True):
# Do something with the table
next_button.click()
time.sleep(2)
next_button = get_next_button()
if 'Next' not in next_button.text:
break
print 'End'
I am not familiar with vba, but if you do not understand Python I can try to translate it to vba.
EDIT
An "approximation" to VBA solution should be this (please check syntax errors, I have never used VBA):
Function GetNextElement() as Object
Dim all_buttons As Object
Dim next_button As Object
all_buttons= driver.FindElementsByXpath("""//*[#id="Price_1_1"]/tfoot/tr/td/div/div/a""")
next_button = all_buttons[all_buttons.Length-1]
Return next_button
End Function
Sub Table_data()
Dim driver As New ChromeDriver
Dim position as Integer
Dim next_button As Object
driver.Get "https://toolkit.financialexpress.net/santanderam"
driver.Wait 1000
next_button = GetNextElement()
Do While True
// Do something with the table
next_button.Click
driver.Wait 2000
next_button = GetNextElement()
position = InStr(next_button.Text,"Next")
If position = 0 Then
Exit Do
End If
Loop
End Sub
I know I could use .FindString for this but for some reason it is not working.
Basically,if listbox items contains just a PART of textbox text,it does action.
Here's the example of not-working code :
Dim x As Integer = -1
x = ListBox1.FindString(TextBox1.Text)
If x > -1 Then
'dont add
ListBox2.Items.Add("String found at " & x.ToString)
Else
End If
The FindString method returns the first item which starts with the search string (MSDN). If you want to match the whole item, you would have to use FindStringExact (MSDN). If you want to perform more complex searches, you would have to iterate through all the elements in the ListBox.
UPDATE:
Code delivering the exact functionality expected by the OP.
For i As Integer = 0 To ListBox1.Items.Count - 1
If (ListBox1.Items(i).ToString.Contains(TextBox1.Text)) Then
ListBox2.Items.Add("String found at " & (i + 1).ToString) 'Indexing is zero-based
Exit For
End If
Next
So, I'm trying to create a function or two that takes html tags and colors them differently than the rest of the text (similar to how Visual Studio does it for key words like Dim). The only way I have found, is to use a rich text box and then do *.SelectionColor = Color.Blue, or something similar. Is there any other way to do this? I made it so whenever the textbox updates, it reads through it at changes all html tags to a different color. This works fine with a really short html file, but when they get to be larger, it takes too long, and the selection moves the cursor around.
So, is there any other way to do this, even if I have to use something other than a rich text box? If not, does anyone see a way to improve this?
Here are the two functions that run when the textbox updates. Tag is blue, attributes are red, stuff in quotes is green.
'//////////////////////////////////////////////////////////////////////////
'// findTag()
'// -finds a tag
'//////////////////////////////////////////////////////////////////////////
Private Function findTag()
Dim tag As String = ""
Dim i As Integer = 0
Dim startTag As Integer
While (i < txtCurrentFile.TextLength - 1)
If txtCurrentFile.Text(i) = "<" Then
startTag = i
While txtCurrentFile.Text(i) <> ">"
tag += txtCurrentFile.Text(i)
i += 1
End While
tag += ">"
colorCode(startTag, tag)
tag = ""
End If
i += 1
End While
Return Nothing
End Function
'//////////////////////////////////////////////////////////////////////////
'// colorCode()
'// -colors different tags accordingly
'//////////////////////////////////////////////////////////////////////////
Private Function colorCode(ByVal startIndex As Integer,
ByVal tag As String)
Dim i As Integer = 0
Dim isAttributes As Boolean = False
Do While (tag(i) <> " " And tag(i) <> ">")
txtCurrentFile.Select(startIndex + i, 1)
txtCurrentFile.SelectionColor = Color.Blue
i += 1
Loop
If i < tag.Length Then
Do Until (tag(i) = ">")
Do Until (tag(i) = Chr(34))
txtCurrentFile.Select(startIndex + i, 1)
txtCurrentFile.SelectionColor = Color.Red
i += 1
Loop
i += 1
Do Until (tag(i) = Chr(34))
txtCurrentFile.Select(startIndex + i, 1)
txtCurrentFile.SelectionColor = Color.Purple
i += 1
Loop
i += 1
Loop
txtCurrentFile.Select(startIndex + i, 1)
txtCurrentFile.SelectionColor = Color.Blue
End If
Return Nothing
End Function
a few suggestions:
Ditch the character scanner. Replace it with anything that is speedier (RegEx, HTML Agility Pack, ...)
If you really want to keep the character scanner, then limit the scan to the area around the modifications (say, 200 characters behind and in front of the cursor)
Remember where the cursor is before you start the color process and restore it when finished.
Implement a background colorizer that does a full file re-color on a separate thread (you'll have to clone the RTB and only apply the changes if the user hasn't made any changes while the colorizer was running).
... I don't know if this would work AT ALL!!!, but it could be cool if it did...
Maybe open the file in a webbrowser control and set your coloring rules in a css sheet??
Again, I don't know if this is a good idea or not, but it might do the trick very nicely since it's already HTML you're dealing with...