I am playing with Windows Script Host VBScript and I am curious if VBScript is capable of adding/removing properties like JScript can.
For example:
var global = this;
var test = function() {
if ('greeting' in global) {
WScript.echo (
'global has property named greeting with value: ' +
global.greeting +
'.'
);
} else {
WScript.echo('global has no property named greeting.');
}
};
test();
global.greeting = 'Hello, World!';
test();
delete global.greeting;
test();
This code determines the global scope (JScript has no initial access to global scope such as window in browsers or global in Node.js, so I have to find it myself).
The test() function checks if the global object has a key named "greeting", and reports its current state as output.
The code does an initial test to show that global object has no greeting key, then sets the greeting property, then does a second test to show that the greeting key has been added to the global object. After this, the greeting property is deleted and a third test is run to show that the key is no longer a part of the global object.
Is this possible to replicate in VBScript?
I know VBScript has Scripting.Dictionary object that can be used to store such information, but I am curious if there is a way to hook existing objects with new properties and delete such properties in VBScript, or if VBScript has no parallel to JScript's {} construct other than Scripting.Dictionary or Classes (whose properties are immutable).
Your specific example could be emulated with something like this:
Set global = CreateObject("Scripting.Dictionary")
Sub test
If global.Exists("greeting") Then
WScript.Echo "global has property named greeting with value: " & _
global("greeting") & "."
Else
WScript.Echo "global has no property named greeting."
End If
End Sub
test
global("greeting") = "Hello, World!"
test
global.Remove("greeting")
test
but in general patching objects, or even regular inheritance, is not supported in VBScript. The best you could do to extend a class is wrap it in a class of your own:
Class MyClass
Private nested_
Public Sub Class_Initialize
Set nested_ = CreateObject("Some.Other.Class")
End Sub
Public Function Foo(val) 'wrapped method
Foo = nested_.Foo(val)
End Sub
Public Function Bar(val) 'patched method
x = nested_.Bar(val)
Bar = x * 42
End Sub
End Class
Related
I need to pass an object and its operation in a function so that each time I can call the function only and save me to write same steps for all the objects like validating the object before performing an operation. Similar way to a Register User Function in QTP/UFT.
However, Testcomplete doesn't have this feature (atleast under my knowledge, would be happy to know if there is)
This is my code that I am trying but unable to:
Call OpenPageorTab("Aliases.Admin.wndMain.toolStrip", ".Visible")
Function OpenPageorTab(obj, method)
'if obj.Exists then
execute a = obj&method
delay(1000)
OpenPageorTab = True
'Else
log.Error "Unable to find the object"
OpenPageorTab = False
'End if
using if condition as i was passing object earlier instead of string
It fails at "execute" statement and gives me VbScript runtime error when executing this statement.
my question is two fold -
How do I pass objects and its operation in a function and execute it
Also is it possible to pass an object it self instead of string for ex:
obtoolbar = "Aliases.Admin.wndMain.toolStrip"
Call OpenPageorTab(obtoolbar, ".Visible")
Appreciate any help or direction on this issue
EDIT 1
I am somewhere close to an answer however not accurately. I am able to pass the object as string - Check the code below
Call OpenPageorTab("Aliases.Admin.wndMain.toolStrip", ".Click")
Function OpenPageorTab(obj, method)
' if obj.Exists then
eobj = "" & obj & method
execute (eobj)
delay(1000)
OpenPageorTab = True
' Else
log.Error "Unable to find the object"
OpenPageorTab = False
' End if
End Function
However I still need to pass the object something like
Set oToolStrip = Aliases.Admin.wndMain.toolStrip
Call OpenPageorTab(oToolStrip, ".Click")
This is something that I'm unable to do.
EDIT 2
I have already got the answer to this problem and have posted the solution. That being said, is there any way that Function can be utilized as a method ?
Here an example of how to reference a function and pass parameteers to it, including objects.
Const forReading = 1, forWriting = 2, forAppending = 8, CreateFile = True
Set my_obj = CreateObject("Scripting.FileSystemObject").OpenTextFile("c:\temp\test.txt", forWriting, CreateFile)
Function my_function(my_obj, method, text)
command = "my_obj." & method & " """ & text & """"
ExecuteGlobal command
End Function
'make a reference to our function
Set proc = GetRef("my_function")
'and call it with parameters, the first being the method invoked
Call proc(my_obj, "WriteLine", "testing")
'cleanup'
my_obj.Close
Set my_obj = Nothing
I was able to finally formulate the solution the below function can work as makeshift register function in TestComplete
Sub test
'Set the Object
Set pToolStrip = Aliases.Admin.wndMain.toolStrip.Button("User Admin")
Call GenericOperationFunc(pToolStrip, ".Click", "N")
'if you want to perform an operation which return a value
b = GenericOperationFunc(Aliases.Admin.wndPopup.Child(2), ".Caption", "Y")
End Sub
Public Function GenericOperationFunc(obj, method, Return)
GenericOperationFunc = False
on error resume next
if obj.Exists then
if Ret = "Y" then
eobj = "NewText="&"obj" & method
execute (eobj)
GenericOperationFunc = NewText
Delay(500)
Else
eobj = "obj" & method
execute (eobj)
delay(1000)
GenericOperationFunc = True
End if
Else
log.Error "Unable to find the object"
GenericOperationFunc = False
End if
End Function
'log.error, delay, aliases, ptoolstrip(object) are testcomplete specific
We have noticed that sometimes from the results of an AJAX call to a controller action that the case of the JSON result is incorrect. The returned case will actually change if we rebuild our solution and try the exact same call. In the following case, the key's case has been correct for over a year until now when it has decided to start randomly changing depending on some seemingly random circumstances.
As you can see in the picture above, the key for the JSON result is lowercase "success". However when I view the results in Chrome's console, it is an uppercase "Success". This is causing our JavaScript to fail since it is checking for the lowercase version.
What is causing this? And more importantly, how do we stop this?
vb.net is case-insensitive as opposed to C# which is case-sensitive. This means that the compiler will generate only one class (from the first instance) for each of the following anonymous types:
Dim a = New With {.success = True} 'Compiler generate a class based on this type
Dim b = New With {.Success = True} 'Same type as `a`
Dim c = New With {.sUcCeSs = True} 'Same type as `a`
Debug.WriteLine(a.GetType().Name)
Debug.WriteLine(b.GetType().Name)
Debug.WriteLine(c.GetType().Name)
VB$AnonymousType_0'1
VB$AnonymousType_0'1
VB$AnonymousType_0'1
Here's how the complied code looks like when compiled back to vb.net:
<DebuggerDisplay("success={success}"), CompilerGenerated> _
Friend NotInheritable Class VB$AnonymousType_0(Of T0)
' Methods
<DebuggerNonUserCode> _
Public Sub New(ByVal success As T0)
Me.$success = success
End Sub
<DebuggerNonUserCode> _
Public Overrides Function ToString() As String
Dim builder As New StringBuilder
builder.Append("{ ")
builder.AppendFormat("{0} = {1} ", "success", Me.$success)
builder.Append("}")
Return builder.ToString
End Function
Public Property success As T0
<DebuggerNonUserCode> _
Get
Return Me.$success
End Get
<DebuggerNonUserCode> _
Set(ByVal Value As T0)
Me.$success = Value
End Set
End Property
Private $success As T0
End Class
A question about scope was raised today and it got me thinking.
I've always understood VBScript scope and how to declare Globally and Locally. It occurred to me though that I never use Public variables but tend to use Dim instead when declaring Globally.
As far as I understood it Dim is the same as Public, but if that's the case why have both of them? Is there a difference between the two and is it good practice to use one over the other?
Update:
This question is aimed purely at VBScript, not VBA and especially in the context of Classic ASP.
Key Differences Between Visual Basic for Applications and VBScript
Access restrictions make sense in modular or object-orientated languages. As Basic started as a simple procedural language, the later addition of such features make strict rules for using Dim, Public, and Private difficult.
Everything below is about VBScript (not VBA, not ASP, not VB.NET)
Facts:
In out of Sub/Function/Class code, Dim, Public, and Private are equivalent: All declarations apply to the global scope
In Subs/Functions/Methods you can use Dim only. The declarations apply to the Func/Sub/Method-local scope
In class definitions, Dim and Public declare variables that are accessible for out-of-classe code; Private creates variables that can be accessed from class-code only. Private methods are callable from class-code only, Public methods (default) can be called from 'outer space'.
Ruleset I:
Use Dim for top-level declarations, because Public/Private have no discernible effect
Use Dim in Subs/Functions/Method, because you must
Use Public or Private (but not Dim) for member variable declarations, because the access permissions are important
Use Private for private methods, but don't bother with Public, because it's the default
Sample code:
Option Explicit
Dim gsDim : gsDim = "gsDim"
Public gsPub : gsPub = "gsPub"
Private gsPriv : gsPriv = "gsPriv"
Class cX
Dim m_sDim
Public m_sPub
Private m_sPriv
Private Sub Class_Initialize()
m_sDim = "m_sDim"
m_sPub = "m_sPub"
m_sPriv = "m_sPriv"
End Sub
Function ToString()
' Public Whatever => syntax error
' Private Whatever => syntax error
Dim Whatever ' => no problem to use Dim for local declaration
ToString = ToStringPriv()
End Function
Private Function ToStringPriv()
ToStringPriv = Join(Array(m_sDim, m_sPub, m_sPriv))
End Function
End Class
Function main()
' Public Whatever => syntax error
' Private Whatever => syntax error
Dim Whatever ' => no problem to use Dim for local declaration
main = 0
WScript.Echo "in func main():", Join(Array(gsDim, gsPub, gsPriv))
Execute "WScript.Echo ""via Execute:"", Join(Array(gsDim, gsPub, gsPriv))"
Dim oX : Set oX = New cX
WScript.Echo "oX.ToString():", oX.ToString()
Dim s
On Error Resume Next
s = oX.ToStringPriv()
WScript.Echo Err.Description
s = oX.m_sPriv
WScript.Echo Err.Description
On Error GoTo 0
End Function
WScript.Echo "top level code:", Join(Array(gsDim, gsPub, gsPriv))
WScript.Quit main()
output:
cscript dimpubpriv.vbs
top level code: gsDim gsPub gsPriv
in func main(): gsDim gsPub gsPriv
via Execute: gsDim gsPub gsPriv
oX.ToString(): m_sDim m_sPub m_sPriv
Object doesn't support this property or method
Object doesn't support this property or method
Update wrt Kul-Tigin's comment:
Ruleset II (when writing code for a host that supports modules):
Treat modules as classes, i.e. apply I.3 and I.4 to your top-level variable rsp. Sub/Function declarations (because now access rights matter/are enforced)
Code:
Option Explicit
Public gsPub : gsPub = "gsPub"
Private gsPriv : gsPriv = "gsPriv"
Class AContext
Public CodeObject
End Class
With (New AContext)
Set .CodeObject = Me
WScript.Echo .CodeObject.gsPub
WScript.Echo .CodeObject.gsPriv
End With
Evidence:
cscript dimpubpriv.vbs
gsPub
... Microsoft VBScript runtime error: Object doesn't support this property or method: 'CodeObject.gsPriv''
Is it possible in VBScript to determine the name of the function currently executing?
In .NET, you could do:
MethodBase method = MethodBase.GetCurrentMethod();
Console.WriteLine(method.Name);
In the past, I build a callstack viewer to see the performance of each function that is called. This needs one extra line of VBS code per function/sub and some overhead during runtime of course because of the extra code.
bottom - up:
Function DoSomething(a, b, c)
dim registerFunctionObj : Set registerFunctionObj = [new RegisterFunction]("DoSomething")
' other code
End Function
Whenever the function is called, it creates a new instance of the RegisterFunction object. When the function exits, the registerFunctionObj variable goes out of scope automatically, calling the Class_Terminate sub of the instance.
[new RegisterFunction] is just a function that return a registerFunction instance:
Function [new RegisterFunction](funcName)
Set [new RegisterFunction] = new cls_RegisterFunction
[new RegisterFunction].FunctionName = funcName
Set [new RegisterFunction].CallStackViewer = CallStackViewer
End function
Class cls_RegisterFunction
Private functionName_, startTime_, callStackViewer_, endTime_
Private Sub Class_Initialize
startTime_ = now
callStackViewer_.LogInitialize me
End Sub
Public Property Let FunctionName(fName)
functionName_ = fName
End Property
Public Property Set CallStackViewer(byRef csv)
Set callStackViewer_ = csv
End Property
Private Sub Class_Terminate
endTime_ = now
callStackViewer_.LogTerminate me
End Sub
End Class
The CallStackViewer instance is a singleton instance of the a CallStackViewer class, but you can make it a part of your project, so you retrieve it through you global project class:
Private PRIV_callStackViewer
Public Function CallStackViewer()
If not IsObject(PRIV_callStackViewer) Then
Set PRIV_callStackViewer = new cls_CallStackViewer
End If
Set CallStackViewer = PRIV_callStackViewer
End Function
Class cls_CallStackViewer
Public Sub Class_Initialize
' Here you can retrieve all function libraries (as text file) extract the
' function name, the file they are in and the linenumber
' Put them in a dictionary or a custom object
End Sub
Public Sub LogInitialize(byref registerFunction)
' Here you can push the function on a stack (use a standard dotnet list>stack for it),
' log the starttime to a log object or handle custom breakpoints
End Sub
Public Sub LogTerminate(byref registerFunction)
' Here you can pop the function from a stack, log the endtime to a log
' object or handle custom breakpoints
End Sub
End Class
Disclaimer: The code in here is pure demo code created on the fly. It lacks functionality and is only here to explain the concept. It could contain errors and is not complete.
The only thing you need is one line of code per function and your own imagination to expand it.
No, but you can easily implement it
dim module_name
sub sub1
module_name = "sub1"
wscript.echo "i'm " & module_name
'do something
end sub
function function1
module_name = "function1"
wscript.echo "i'm " & module_name
function1 = "something"
end function
In case of recursion you could also remember the level you'r in so that you can get out if getting too deep.
I would like to create an object of a VB6 class and call a method on that object. If an error occurs in
that method I would like to be able to raise an error in the method and catch it outside the class, in the
routine which I called the method.
The class for example may contain:
Const cmlngMYERROR As Long = vbObjectError + 1001
Public Sub MyMethod()
...
Err.Raise cmlngMYERROR, Err.Source, Err.Description
End Sub
The calling routine may contain:
Private Sub MyCallingRoutine()
Dim objMyObject As ClassName
On Error GoTo ErrorHandler
Set objMyObject = New ClassName
objMyObject.MyMethod
Exit Sub
ErrorHandler:
If Err.Number=clngMYERROR Then
...
End If
End Sub
The problem I have is that you cannot define public constants at the top of a class. Therefore, you cannot
check the error number using the defined constant in the calling routines error handler. What is the best
practise to check the error code in the calling routine? Am I using error handling correctly in this
example (or on the right track at least)?
Thanks in advance.
There is a clever (ugly?) trick to emulate a public constant: use a public Enum instead
Public Enum PseudoConst
cmlngMYERROR = vbObjectError + 1001
End Enum