QTP: Object Required errors when Object is valid - vbscript

I have the following VBScript in a reusable action:
'Gather links
Browser("1").Navigate "http://InternalWebmail/something/inbox.nsf"
set oDesc = Description.Create()
oDesc("micclass").Value = "Link"
set links = Browser("1").Page("Webmail").ChildObjects(oDesc)
Dim links2
links2 = ""
'Filter out irrelevant links
For i = 0 To links.Count-1
If lcase(trim(links(i).GetROProperty("text"))) = lcase(trim(DataTable("ExpectedFrom", dtGlobalSheet))) Then
links2 = links2 + "," + links(i).GetROProperty("url")
End If
Next
Dim final
final = split(mid(links2,2),",") 'Remove leading comma and split into array
'For each link (i.e. for each E-mail received):
'Effectively giving a reusable action an input parameter, I hope
For i = 0 To final.Count - 1 'error: Object Required
DataTable("url","CheckHeader") = final(i)
RunAction "CheckHeader", oneIteration
Next
Everything runs just fine, until I get to the declaration of the loop at the bottom of the snippet. There, QTP gives me an error "Object Required" and refuses to elaborate.
i has a leading value of 58, though I've tried setting it to 0 prior to entering the loop.
final is an array of 6 strings, each a URL. All have a value.
If I msgbox(final(2)), I see the value of final(2) as being valid.
isobject(final(1)) = false
final(1) has the correct value
msgbox(final is nothing) and msgbox(final(1) is nothing) yield the same error.
It looks as if the array is null but somehow the array has members?
How is this possible?
What is QTP talking about?

In vbscript arrays don't have a Count property, you should use UBound
x = split("how now brown cow")
' MsgBox x.Count ' error
MsgBox UBound(x) ' 3
The reason .Count worked for the first loop is that ChildObjects does not return an array, it returns a COM collection object. That is also why you had to use the Set statement when assigning to links but not when assigning to final.

Related

VB Control Naming by Variable

Dim LineNo as Integer
LineNo = CStr(channel) 'This can have a value of 1 to 100
If LineNo = 1 then
Text1.Text = "Line one selected"
Elseif LineNo = 2 then
Text2.Text = "Line one selected"
'Etc etc
End if
I need to replace the number "1" in Text1.Text and every other TextBox with the value of LineNo? For example:
Text{LineNo}.Text
So I would not have to do a repeated "If" and have a smaller one line code like this:
Text{LineNo}.Text = "Line " & LineNo & " selected"
How would I do this?
Look into a Control array of text boxes. You could have txtLine(), for example, indexed by the channel number.
LineNo = CStr(channel)
txtLine(channel).Text = "Line " & LineNo & " selected"
To create the array, set the Index property of each of the text boxes to an increasing integer, starting at 0.
If you have a finite and relatively small number, you can use a property. I've used this approach with up to 30+ elements in a pinch. Super simple, easy pattern to recognize and replicate in other places. A bit if a pain if the number of elements changes in the future, but extensible nevertheless.
It uses the Choose statement, which takes an index N and returns the Nth element (1-based), hence, the check makes sure that N is > 0 and <= MAX (which you would configure).
Public Property Get TextBox txt(ByVal N As Long)
Const MAX As Long = 10
If N <= 0 || N > MAX Then Exit Property ' Will return a "Nothing". You could return the bound element if you prefer
set txt = Choose(Text1, Text2, Text3, Text4, Text5, Text6, Text7, Text8, Text9, Text10)
End Property
Then, you can simply reference them with the Property, much like an alias:
txt(1).Text = "Line 1 text"
txt(2).Text = "Line 2 text"
If you have an arbitrary number, then you are likely using a control array already, which is simpler because it can be referenced by Index already, so you can directly reference it.
If neither of these work for you and you have a very large number of controls, you can scan the Controls collection in a similar Property, attempting to match ctrl.Name with the pattern of your choice (e.g., matching the first 4 characters to the string "Text", thus matching Text1, Text2, etc, for an unlimited number). Theoretically, this should be future-proofed, but that's just theoretical, because anything can happen. What it does do for you is to encapsulate the lookup in a way that "pretends" to be a control array. Same syntax, just you control the value.
Public Property Get TextBox txt(ByVal N As Long)
Dim I As Long
For I = 0 To Controls.Count - 1 ' Controls is zero-based
' Perform whatever check you need to. Obviously, if you have a "Label" named
' "Text38", the assignment will throw an error (`TextBox = Label` doesn't work).
If Left(Controls(I).Name, 4) = "Text" Then
set txt = Controls(I)
End If
Next
' If you want the Property to never return null, you could uncomment the following line (preventing dereference errors if you insist on the `.Text` for setting/getting the value):
' If txt Is Nothing Then Set txt = Text1
End Property
Use the same way as above: txt(n).Text = "..."

Creating a copy of a recordset in a Session variable

This is a follow on from a question I asked here > Converting an HTML table to JSON to pass to an AJAX call for downloading as a CSV
I have a report page that outputs a number of recordsets as graphs and tables but also buttons to "download as a CSV file" I have a generic function that will take any number of recordsets (as the stored proc returns multiple recordsets) and outputs a CSV so thats fine.
The issue is I want to set the output of my stored proc into a Session("DATA") variable and then create a "copy" of the data in memory so that whenever the "download" button is pressed I can just look for a Session("DATA") variable and output it if it exists.
The problem is that when I set a recordset object to point at the Session it is referential so that once it has looped through all the recordsets outputting them on the page the Session is then empty (or at the end of all the recordsets - so it's an object with no data in it)
How can I Create a "copy" of the recordset instead of a pointer so that the Session always has the full recordset in it e.g
Set Session("DATA") = objCon.Execute(strSQL) '* returns multiple recordsets
Set objRS = Session("DATA")
Do While Not objRS.EOF......
'* BUT now when I want to access Session("DATA") it is at the start of all the recordsets and not a spent, EOF of the last recordset due to me looping through objRS
I could have a function that loops through the recordsets and makes a duplicate but then that seems like a lot of effort and performance and I thought there must be a way to copy the recordsets for the session somehow without looping through it multiple times.
If I have to create a "Copy" object function then I suppose I will have to but is there not an easier way in ASP CLASSIC to create a copy of an object and not a reference pointer?
You can read the entire recordset into an array, using GetRows:
'GetDataSet
' Returns a table of data based on the supplied SQL statement and connection string.
'Parameters:
' sqlString (string) - The SQL string to be sent. This can be either a valid SQL string or an Application setting
' specified using the '#' prefix (e.g. #GET_USERNAME)
' connString (string) - The database connection string. Either a valid connection string, an Application setting
' (using the '#' prefix, e.g. #CONN_STRING) or an AMC (AppModeConnection string).
'Usage:
' dataSet = GetDataSet(sqlString, connString)
'Description:
' This function generates a table of information in a 2 dimensional array. The first dimension represents the columns
' and the second the rows. If an error occurs while the routine is executing the array and the base index (0,0) is set
' to C_ERROR, (0,1) to the VBScript error index, and (0,2) to the VBScript error description.
'Notes:
' Updated this function to take advantage of the AppModeConnection feature.
'Revisions:
' 30/09/2015 1.1 Added facility to allow recovery of Application settings as Query and connection strings using
' '#', (e.g.: ds = GetDataSet("#GET_USER_DETAIL", "#CONN_DATABASE")
' 25/09/2015 1.0 Added AMC support for Classic ASP. The system will test to see if there is a valid connection
' string based on the current application mode and the connection string provided (e.g. if the
' connection string is 'CONN_DATABASE' and the application mode is 'DEV' then the final connection
' string will be 'CONN_DATABASE_DEV'. A connection string should be present to cover this.
' < 25/09/2015 0.1 Bug ironed out that prevented closing of the database.
' < 25/09/2015 0.0 Initial version.
function GetDataSet(ByVal sqlString, ByVal connString)
'Test to see if there's an application connection string first...
If Left(connString, 1) = "#" Then
connString = Application(Mid(connString, 2))
Else
Dim amc
amc = AppModeConnection(connString)
If amc <> "" then connString = amc
End If
'Test the SQL string to see if it's stored as an Application setting...
If Left(sqlString, 1) = "#" Then sqlString = Application(Mid(sqlString, 2))
'Define the initial output...
dim rV, rs
If (Application("APP_MODE") = Application("MODE_DEV") And Application("DEV_TRAP_ERRORS")) Or _
(Application("APP_MODE") <> Application("MODE_DEV")) Then On Error Resume Next
'Define and open the recordset object...
set rs = Server.CreateObject("ADODB.RecordSet")
rs.Open sqlString, connString, 0, 1, 1
'Initialise an empty value for the containing array...
redim rV(0,0)
rV(0,0) = C_NO_DATA
'Deal with any errors...
if not rs.EOF and not rs.BOF then
'Store the data...
rV = rs.GetRows()
'Tidy up...
rs.close
set rs = nothing
select case err.number
case 3021 'No data returned
'Do nothing as the initial value will still exist (C_NO_DATA)
case 0 'No error
'Do nothing as data has been returned
case else
redim rV(4,0)
rV(C_COL_IDENTIFIER,0) = C_ERROR
rV(C_COL_ERROR_ID,0) = err.number
rV(C_COL_ERROR_MESSAGE,0) = err.description
rV(C_COL_SQL,0) = sqlString
rV(C_COL_CONNECTION,0) = "Withheld"
end select
end if
on error goto 0
'Return the array...
GetDataSet = rV
end function
This is my own in depth version which does some funky stuff with connection strings etc, so feel free to use it, but note that you'll have to set-up the handling for the connection strings etc. Within the code, though, is the core element - the GetRows, that you require.
You shouldn't need to set any Session variables, simply process all in the same page, as per marekful's answer to your post. You can do this using a simple For...Next loop using an array.
To use the function above simply declare your SQL and call it like so...
Dim ds, sql
sql = "EXEC prc_get_report_data "
ds = GetDataSet(sql, "#my_conn")
(Note: read the code comments about the connection strings).
The array returned from this is obviously two dimensional zero based, where x = columns, y = rows:
ds(x, y)
What I tend to do is define constants to cover the column names, matching them to the equivalents in the database...
Const COL_ID = 0 'Column 0 is the ID field (note zero based)
Const COL_TITLE = 1 'Title field
Const COL_DESCRIPTION = 2 'Description field
...and so on.
Then you can reference them eaasily:
If ds(COL_ID, row) = 14 Then
Use the UBound function to get the extents of the array...
Dim row, rows
For rows = 0 To UBound(ds, 2) '2 is the second dimension of the array (note not zero based
If ds(COL_ID, row) = avalue Then
You get the idea.

Why does this classic ASP loop to split a textarea into lines not work?

I am trying to put a textarea into an array, splitting it where there are new lines. I know that docnos does in fact contain the characters found within the textarea. Can anyone please tell me why this doesn't work? It is not putting anything into the array for some reason.
docnos = dbencodeStr(Request.form("docnos"))
Dim myArray
myArray = Split(docnos, vbCrLf)
for i = 0 to UBound(myArray,2)
tempstr = trim(myArray(0,i))
strSQL = "insert into DocumentNumbers (queryid, documentnumber) values('"&queryid&"','"&tempstr&"');"
Response.write(strSQL)
Response.write(tempstr)
Set rs = TransactionQueriesConn.Execute(strSQL, ,adCMdTExt)
next
Given myArray = Split(docnos, vbCrLf), UBound(myArray,2) will throw an "Subscript out of range" error (no second dimension). Remove the "On Error Resume Next".
Assuming myArray holds an one dimensional array of lines, the loop needs UBound(myArray) or UBound(myArray,1).
If docnos contains "234234234<BR>567567<BR>345345345", you need to Split on <BR>. Evidence:
>> s = "234234234<BR>567567<BR>345345345"
>> WScript.Echo Split(s, "<BR>")(1)
>>
567567

How to remove duplicate from an array using vb script without using dictionary objects?

I am trying to remove duplicates from array using for loop and conditional statement.But I am unable to create new array without any duplicates.There is xls having country name with duplicates,i am aiming to remove duplicates and create a new array with unique country names.
For e.g
strFilePath="D:\Country.xls"
Set objExcel = CreateObject("Excel.Application")
objExcel.Visible=True
Set objWorkbook = objExcel.Workbooks.Open (strFilePath)
Set objSheet=objExcel.Sheets("Country")
objExcel.DisplayAlerts = False
objExcel.AskToUpdateLinks = False
objExcel.AlertBeforeOverwriting = False
Dim A(100)
Dim B(100)
For i = 2 To 6 Step 1
k = i-2
A(k)=objSheet.Cells(i,1).Value
Next
B(0)=A(0)
For j = 0 To 4 Step 1
strIt=A(j)
For m = 1 To 4 Step 1
reslt = StrComp(A(m),strIt,1)
If(reslt = 1 Or reslt = -1) Then
c=1
B(c)=A(m)
c=c+1
End if
m=m+1
Next
Next
Two options, depending on your needs:
Try using a hash table of the country names. When entering values in to the hash table you could do a simultaneous check to see whether you encounter an identical value. If it finds one it will abort entering the new value and continue with the next one, otherwise it will be entered in to the table. At the end of it you will have your list of unique country names.
Sort the list of countries and then do a second pass that removes duplicate countries (since duplicates will now be grouped together)
Problems with both of these methods is that they dont preserve original order unless you keep some sort of "original index" value and then sort based on that value once you remove duplicates.
Here's how I usually do it:
Dim uniqueentries()
ReDim uniqueentries(-1)
' Here you could go through your existing array and
' call "GetUniqueEntries" sub on each entry, e.g.
For Each i In oldarray
GetUniqueEntries i
Next
Sub GetUniqueEntries(newentry)
Dim entry
If UBound(uniqueentries) >= 0 Then ' Only check if uniqieentries contains any entries
For Each entry In uniqueentries
If newentry = entry Then Exit Sub ' If the entry you're testing already exists in array then exit sub
Next
End If
ReDim Preserve uniqueentries(UBound(uniqueentries) + 1) ' Increase new array size
uniqueentries(UBound(uniqueentries)) = newentry ' Add unique entry to new array
End Sub
This could be done more simpler way by using Split command. Please check the below solution, if any clarification let me know.
Dim aDupl
Dim aNew, strNew
aDupl = Array("A", "B", "A", "D", "C", "D")
strNew = ""
For iCnt= 0 To UBound(aDupl)
If InStr(strNew,aDupl(iCnt) ) = 0 Then
strNew =strNew&aDupl(iCnt)&","
End If
Next
aNew = Split(strNew, ",")
For iCnt=0 To UBound(aNew)
WScript.Echo aNew(iCnt)
Next

How to use substring in vbscript within a xsl page

I am trying to replace the double quotes in a string with a single quote, got the following code but get error message saying "Object Required strLocation"
Sub UpdateAdvancedDecisions(strLocation)
Dim d
Dim strLLength
strLLength = Len(strLocation) - 1
For d = 0 To strLLength
alert strLocation
strValue = strLocation.Substring(2,3)
If strLocation.substring(d,d+1)=" " " Then
strLLength = strLLength.substring(0, d) + "'" + strLLength.substring(d + 1,strLLength.length)
Next
End Sub
As Helen said, you want to use Replace, but her example assigned the result to your weird strLLength variable. Try this instead:
strLocation = Replace(strLocation, """", "'")
This one line does the job you asked about and avoids all the code currently in your given subroutine.
Other things that are problems in the code you posted:
a variable holding a number like the length of a string would not have a "str" prefix, so strLLength is misleading
strings in VBScript are indexed from 1 through length, not 0 through length-1
there is no "alert" keyword in VBScript
you assign a value to strValue, then never use it again
you need to use Mid to get a substring, there is no "substring" string method in VBScript
c = Mid(strLocation, d, 1) ' gets one character at position d
The more I look at this, the more clear it is that its some JavaScript that you're trying to run as VBScript but are not translating at all correctly.
Use a reference for VBScript like one of the following:
MSDN Library: VBScript Language Reference
W3Schools VBScript Tutorial

Resources