GetelementsByTagName seems to not work properly - html

this question sounds stupid but how come when I use the function GetElementsByTagname("frame") , it only returns 3 as a length and not 5 as I expected ?
Here is the HTML of the webpage where I counted 5 times the apparition of the tagname "frame" but when I ask for the length in VBA I get 3...
My observations :
1) You can see that 3 is the number of main frames (top_navigation, contentframe, dummyframe)
2) If I try to access to one of the mainframes via getelementbyname, it works but if I try to access on the the subframes of contentframe ( leftnavigation or postfachcontent) it doesn't work ( 0 item detected)
Here is my code :
Dim Frame As IHTMLElementCollection
Set Frame = IEDoc.getElementsByName("contentframe") ' this works and returns 1 item
MsgBox Frame.Length
Set Frame = IEDoc.getElementsByName("postfachcontent")
MsgBox Frame.Length ' this returns 0 item
Dim Collection As IHTMLElementCollection
Set Collection = IEDoc.getElementsByTagName("frame")
MsgBox Collection.Length ' this returns 3 and I expected 5...

Only 3 frames are on that page, the rest are inside an embedded html frame which getElementsByTagName cannot access as it is a different DOM tree.

Related

Can I store the same option value for more than one toggle button in a control box?

I have a list of questions each with 4 possible answers that are displayed as toggle buttons on my form. What I want to do is if the user chooses either of the first two buttons, the Option Value stored is "1", if they choose either of the last two buttons, the Option Value stored should be "0". The option values must be different for each toggle button in a control group. Is there a way to recode the toggle buttons to store the desired response? (I work in psychology, thus the bait and switch of offering 4 choices to the user when really only two responses are recorded).
Here is what I have tried:
I tried thinking about a recode as jcarroll suggested, but this is a circular reference:
Private Sub Q1_Click()
If Me.Q1 = 1 Or 2 Then
Me.Q1 = 1
Else:
Me.Q1 = 0
End If
End Sub
I could recode into another variable but that is just as clunky as using a SQL statement on the data post-hoc, for instance:
NewVariable=Iif([Q1]=1,1,iif([Q1]=2,1,0)
Finally, I tried to code have both toggle buttons have the same Option Value (which causes both to look pressed if either is pressed) and recode the unpressed toggle button's back color. But while my code looks correct to me, this did not change the pressed color of the toggle button (which I think has to do with over riding toggle button design settings):
Private Sub Frame5_Click()
If Toggle8.Value = True Then
Toggle9.BackColor = &HFF00&
Else
Toggle9.BackColor = &H8000000F
End If
End Sub
I could not come up with any programming solutions on the form itself to solve this. The alternative is that I wrote a procedure to apply to the data after it is collected which will be stored as 1,2,3,4 to convert it to 0 or 1. This procedure also sums up the 1's. I have 50 variables/questions that will be passed through this procedure (as well as another like it that converts 3&4 to 1).
Public Function Recode1(ParamArray arg()) As Variant
Dim Size As Integer, skips As Integer, i As Integer, result As Variant
'Recodes first two toggle buttons as "1" for AQ assessment
Size = UBound(arg) + 1
For i = 0 To Size - 1
If IsNull(arg(i)) Or Not (IsNumeric(arg(i))) Or arg(i) = -99 Then
skips = skips + 1
Else
If arg(i) = 1 Or arg(i) = 2 Then
result = result + 1
Else:
result = result + 0
End If
End If
End sub

Compare index of 2 elements in a collection

Issue : I have some issues figuring out a way to select elements in my HTMLDocument which are under a certain point in the page.
In the following code sample, as you can see in the comments, I first select a part of them which respect my queryselector criteria
IEDoc.querySelectorAll("td[width='100'][class='ListMainCent'][rowSpan='1'][colSpan='1']")
In this example I have 10 elements in this collection. Each of this element in contained in a table which is its parent on the 7th degree.
MsgBox TypeName(IEDoc.querySelectorAll("td[width='100'][class='ListMainCent'][rowSpan='1'][colSpan='1']")(2).ParentNode.ParentNode.ParentNode.ParentNode.ParentNode.ParentNode.ParentNode) ' HTMLTable
Some of those elements are in the same table.
You can see here the form which contains all the tables .
Now, the thing is that I want to select the innerHTML of some of those elements only and not all of them. The criterion to know if I one of those 10 elements interests me or not is it's position on the webpage. I want all the elements which are under the message Part Usage. There is only one table containing the Part Usage text and so my idea was to see if the table in which are contained each element has a higher or lower index in the "form" collection.
If the index is higher I want this element, otherwise I discard it.
What I did for this is the following code :
I set the ID Bim to all the tables containing one or more
from the 10 elements.
For Each Element In IEDoc.querySelectorAll("td[width='100'][class='ListMainCent'][rowSpan='1'][colSpan='1']") ' here for all of the 10 numbers found with the queryselectorall we'll find their respective table in the collection (form) and set its Class as "Bim". But since some of the numbers are in the same table, we won't have 10 tables with a classname "Bim" at the end of the process. We'll have only x tables with the classname "Bim"
Element.ParentNode.ParentNode.ParentNode.ParentNode.ParentNode.ParentNode.ParentNode.Class = "Bim"
Next
I set the ID Stop to the table containing the text Part Usage
For Each Element In IEDoc.getElementsByClassName("SectionHead")
If Element.innerHTML = "Part Usage" Then
'MsgBox TypeName(Element.ParentNode.ParentNode.ParentNode)' HTMLTable
Element.ParentNode.ParentNode.ParentNode.ID = "Stop"
End If
Next
I check which tables with the Classname Bim are under (=higher index) the table with the ID Stop. For the table ( there is actually only one) matching the criterion of point 3 I apply IEDoc.querySelectorAll("td[width='100'][class='ListMainCent'][rowSpan='1'][colSpan='1']") inside of them so that I get all the elements in contains and more paricularly their innerHTML.
For Each Element In IEDoc.getElementsByClassName("Bim") ' Here we check all the x tables which have the Classname "Bim"
If Element.indexInTheWholeForm > IEDoc.getElementById("Stop").indexInTheWholeForm Then 'and compare somehow if their index in the (form) collection if higher than the table with the ID "Stop" ( this is similar to checking if the element if lower on the webpage in thic case) ( we only want the element which have a higher index aka under the Part Usage table)
For Each Element2 In Element.querySelectorAll("td[width='100'][class='ListMainCent'][rowSpan='1'][colSpan='1']") ' Now we are in the table which contains the part numbers and we'll look for all the part numbers it contains by applying the queryselectorall again, but this time only in this specific table
array_parts2(iteration2) = Element.querySelectorAll("td[width='100'][class='ListMainCent'][rowSpan='1'][colSpan='1']")(iteration2).innerHTML
ActiveWorkbook.Worksheets(1).Cells(iteration2 + 1, 19) = array_parts2(iteration2)
iteration2 = iteration2 + 1
Next
End If
Next
of course what doesn't work is the indexInTheWholeForm property which doesn't exist. Any ideas on how to do this ?
Thank for reaching that line :)
Untested but I would do something like this (assuming I understood you correctly)
Sub Tester()
Const S_MATCH As String = "td[width='100'][class='ListMainCent'][rowSpan='1'][colSpan='1']"
Dim e, tbl, bHit As Boolean
'...
'load page etc
'...
'get all the matching rows and cycle though them
For Each e In IEDoc.querySelectorAll(S_MATCH)
'did we get to the table of interest yet?
If Not bHit Then
Set tbl = e.ParentNode.ParentNode.ParentNode.ParentNode. _
ParentNode.ParentNode.ParentNode
If IsPartUsageTable(tbl) Then bHit = True
End If
If bHit Then
'we reached the table of interest, so
' do something with e
End If
Next
End Sub
Function IsPartUsageTable(tbl) As Boolean
Dim e, rv As Boolean
For Each e In tbl.getElementsByClassName("SectionHead")
If Element.innerHTML = "Part Usage" Then
rv = True
Exit For
End If
Next
IsPartUsageTable = rv
End Function
Ok, so as unexpected as it sounds, I think I found a solution to my own question. I will confirm you that it works as soon as I have the possibility to run it with my colleague.
So I keep point 1 and 2 from my initial post and I replaced point 3 with the following :
For i = 0 To IEDoc.getElementsByTagName("form")(0).getElementsByTagName("table").length
If IEDoc.getElementsByTagName("form")(0).getElementsByTagName("table")(i).ID = "Stop" Then
index_Part_Usage = i
Position_Part_Usage = index + 1
Exit For
End If
Next
'MsgBox Position_Part_Usage
For i = 0 To IEDoc.getElementsByTagName("form")(0).getElementsByTagName("table").length
If IEDoc.getElementsByTagName("form")(0).getElementsByTagName("table")(i).className = "Bim" Then
index = i
Position = index + 1
If index > index_Part_Usage Then
For Each Element2 In IEDoc.getElementsByTagName("form")(0).getElementsByTagName("table")(i).querySelectorAll("td[width='100'][class='ListMainCent'][rowSpan='1'][colSpan='1']") ' Now we are in the table which contains the part numbers and we'll look for all the part numbers it contains by applying the queryselectorall again, but this time only in this specific table
array_parts2(iteration2) = IEDoc.getElementsByTagName("form")(0).getElementsByTagName("table")(i).querySelectorAll("td[width='100'][class='ListMainCent'][rowSpan='1'][colSpan='1']")(iteration2).innerHTML
ActiveWorkbook.Worksheets(1).Cells(iteration2 + 1, 19) = array_parts2(iteration2)
iteration2 = iteration2 + 1
Next
End If
End If
Next i

VBA Code Stops Working

The following code is called everytime the form is opened. It works great until the 5th opening and then misses deleting one of the controls. Anyone know why this is?
For Each cb In Forms(frmName).Controls
If cb.ControlType = acCheckBox Then
If Left(cb.Name, 3) = "clr" Then
DeleteControl frmName, cb.Name
End If
ElseIf cb.ControlType = acLabel Then
If Left(cb.Name, 3) = "clr" Then
DeleteControl frmName, cb.Name
End If
End If
Next
When you delete an item from a collection in Access the next item moves into that items spot.
Thus when it comes to deleting items from a collection you must start at the end of the collection and go backward.
So replace the
For Each cb In Forms(frmName).Controls
line with
For counter = Forms(frmName).Controls.Count - 1 To 0 Step -1
set cb = Forms(frmName).Controls.Item(counter)
My next question though is what is your overall objective? It's unusual to be manipulating controls in design view programmatically.

How do I keep colors consistent from chart to chart in Reporting Services 2005?

I created a custom color palette for my charts using a technique described on TechNet.
I also have a series of drill-through column charts, where you click on one column and it passes a parameter through to the next chart and so on, giving the appearance of drill-down.
My graphs consist of 3 types of labor, and have three colors on the main chart. When I drill down to the next chart, some of the categories do not have all three types of labor that the main one has. So the first color in the palette is assigned to the series, even though it was the second color on the previous chart. I'd like to avoid this, if possible.
So a data value is green on the first chart (2nd in the color order) and yellow on the next chart (1st in the color order). How do I make the graphs "remember" the total number of series groups that were in the first chart?
This is Reporting Services 2005.
You cannot fix this using custom colour palettes.
What you can do is assign the labour type a colour in the database (using HEX is easiest). Then pass that in in your data set. Then set the color property to you hex value.
Unfortunately this is not possible. I've been looking for this for quite some time...
I was able to solve this because I was using a custom color palette, implemented as a hash table. I basically serialized this information and passed it to a hidden parameter on the subreport and then reinflated the data structure.
It's not perfect, but it works for now.
' Define some globals, including the color palette '
Private colorPalette As String() = _
{"#FFF8A3", "#A9CC8F", "#B2C8D9", "#BEA37A", "#F3AA79", "#B5B5A9", "#E6A5A4", _
"#F8D753", "#5C9746", "#3E75A7", "#7A653E", "#E1662A", "#74796F", "#C4384F", _
"#F0B400", "#1E6C0B", "#00488C", "#332600", "#D84000", "#434C43", "#B30023"}
' color palette pulled from SAP guidelines '
' http://www.sapdesignguild.org/resources/diagram_guidelines/color_palettes.html '
Private count As Integer = 0
Private colorMapping As New System.Collections.Hashtable()
' Create a custom color palette '
Public Function GetColor(ByVal groupingValue As String) As String
If colorMapping.ContainsKey(groupingValue) Then
Return colorMapping(groupingValue)
End If
Dim c As String = colorPalette(count Mod colorPalette.Length)
count = count + 1
colorMapping.Add(groupingValue, c)
Return c
End Function
' In custom actions of the data value, set the results of this '
' function to the mapping parameter in the next report '
Public Function PassColorMapping() As String
If colorMapping.Count = 0 Then
Return Nothing
End If
Try
' convert the hashtable to an array so it can be serialized '
Dim objHash As Object()() = ToJaggedArray(colorMapping)
' serialize the colorMapping variable '
Dim outStream As New System.IO.StringWriter()
Dim s As New System.Xml.Serialization.XmlSerializer(GetType(Object()()))
s.Serialize(outStream, objHash)
' move on to the next report '
Return outStream.ToString()
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Function
I ran into an issue where I couldn't find the equivalent of the onLoad event for the report. Since I wasn't sure where to put this inflate code, I stuck it in the background color of the plot area. Hence I always return "WhiteSmoke". I'll change this if I can find the right place to put it.
' Call this function when the report loads to get the series groups '
' that have already been loaded into the custom color palette '
' Pass in the parameter used to store the color mapping '
Public Function InflateParamMapping(ByVal paramMapping As Parameter) As String
Try
If paramMapping.Value Is Nothing Then
Return "WhiteSmoke"
ElseIf colorMapping.Count = 0 Then
Dim pXmlized As String = paramMapping.Value
' deserialize the mapping parameter '
Dim s As New System.Xml.Serialization.XmlSerializer(GetType(Object()()))
' get the jagged array and convert to hashtable '
Dim objHash As Object()() = DirectCast(s.Deserialize(New System.IO.StringReader(pXmlized)), Object()())
' stick the result in the global colorMapping hashtable '
colorMapping = ToHashTable(objHash)
count = colorMapping.Count
End If
Catch ex As Exception
' MsgBox(ex.Message) '
End Try
Return "WhiteSmoke"
End Function
ToJaggedArray() and ToHashTable() are helper functions because a HashTable is not serializable since they implement an IDictionary. I was in a hurry so I just converted them to an array right quick. Code comes from the Collection Serialization in ASP.NET Web
Services article written by Mark Richman. I converted the code from C# to VB.NET to use in the report.
Public Function ToJaggedArray(ByVal ht As System.Collections.HashTable) As Object()()
Dim oo As Object()() = New Object(ht.Count - 1)() {}
Dim i As Integer = 0
For EAch key As Object in ht.Keys
oo(i) = New Object() {key, ht(key)}
i += 1
Next
Return oo
End Function
Public Function ToHashTable(ByVal oo As Object()()) As System.Collections.HashTable
Dim ht As New System.Collections.HashTable(oo.Length)
For Each pair As Object() In oo
Dim key As Object = pair(0)
Dim value As Object = pair(1)
ht(key) = value
Next
Return ht
End Function
Now in the report itself you need to do a couple things.
Add a reference to System.Xml in Report Properties in both reports.
In the Actions of your parent report, set the Parameter containing your data structure to =Code.PassColorMapping()
In the Plot Area section of your report, put this expression for the background: =Code.InflateParamMapping(Parameters!colorMapping)
And of course, in the Fill for your data Series Style on both charts put this expression: =Code.GetColor(Fields!Type.Value)
You can continue doing this for as many subreports as you want - I currently have 3 levels of drill-through and it works fine.
I solved that extremely easy.
In my parent report I have lets say 12 series fields, each one getting their own color in a chart, on my child report I just keep all series on the chart, for instance going from a column chart to a line chart using drill down, but I control the visibility of them...
So in the child report in Series Properties -> Visibility I just add an expression:
=(Fields!ContentType.Value <> Parameters!ContentType.Value)
This way the report only keeps the visibility of the clicked value and hides all the others and the colors remains the same :)

How do you get a unique id for a layer or generate one in Arcmap?

Is there a way in arcobjects to get a unique id for a layer? If you do a search by layer name there could be possible duplicates.
If there isn't a property is there a way to generate an id?
I tried using the GetHash() but that didn't stay consistent.
There is an ArcObjects Interface present for setting or getting an Id for a layer.
You should look at ILayerDescriptor:ID,
http://resources.esri.com/help/9.3/ArcGISDesktop/ArcObjects/esriCarto/ILayerDescriptor_ID.htm
Here is a VBA Snippet which shows how it can be used:
Public Sub layerInfo()
Dim app As IApplication '
Set app = Application
Dim mxDoc As IMxDocument
Set mxDoc = app.Document
Dim myMap As IMap
Set myMap = mxDoc.ActiveView
Dim mapServer As IMxdServer
Set mapServer = New MxdServer
'''Point to your .mxd...
mapServer.Start ("D:\Test.mxd")
Dim myArray As IArray
Set myArray = mapServer.LayerDescriptors(myMap.Name)
MsgBox myArray.Count
Dim x As ILayerDescriptor
Dim intX As Integer
intX = 0
For intX = 0 To myArray.Count - 1
Set x = myArray.Element(intX)
MsgBox x.ID
MsgBox x.Name
Next
End Sub
It isn't pretty, but in the past I've appended a guid in the layer description. Something like this:
<LAYER guid='a9843c88-3caa-4953-ad96-ca9990b410e9' revision='1' />
I've got a DLL floating around that would slam these xml frags into each layer of an MXD (with enough cr/lf in front to scroll the xml fragment out of the layer description in ArcMap Layer Prop dialog) .
There's a help file in the 7z file (documentation is sparse because I'm doing other things):
http://code.google.com/p/umbriel/downloads/list
I like the idea of using a GUID. This can then be stored in the ModelName property which is a tool for developers of custom objects to use to guarantee the names of objects independent of the true name or alias name.
There are more details and sample code at http://geographika.co.uk/?p=58
Easy. A side effect of using COM and because how the vtables are laid out, is that you can use the memory address of the layer itself as your unique identifier. Inside the implementation of many ESRI GeoDatabase and Carto code itself, this trick is being used all over the place.