I am new to class programming in vba.
Here is my first attempt using teaching of
:Asynchronous HTTP POST Request in MS Access
I am using word vba.
Here is my CXMLHTTPHandler.cls
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "CXMLHTTPHandler"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Dim m_xmlHttp As MSXML2.XMLHTTP
Public Sub Initialize(ByRef xmlHttpRequest As MSXML2.XMLHTTP)
Set m_xmlHttp = xmlHttpRequest
End Sub
Sub OnReadyStateChange()
Attribute OnReadyStateChange.VB_UserMemId = 0
Debug.Print m_xmlHttp.readyState
If m_xmlHttp.readyState = 4 Then
If m_xmlHttp.Status = 200 Then
msgbox m_xmlHttp.responseText
Else
msgbox "Something Went Wrong"
End If
End If
End Sub
Here is my standard module
Option Explicit
Public xmlHttpRequest As MSXML2.XMLHTTP
Function sasynchreq(url As String)
On Error GoTo FailedState
If Not xmlHttpRequest Is Nothing Then Set xmlHttpRequest = Nothing
Dim MyXmlHttpHandler As CXMLHTTPHandler
Set xmlHttpRequest = New MSXML2.XMLHTTP
'Create an instance of the wrapper class.
Set MyXmlHttpHandler = New CXMLHTTPHandler
MyXmlHttpHandler.Initialize xmlHttpRequest
'Assign the wrapper class object to onreadystatechange.
xmlHttpRequest.OnReadyStateChange = MyXmlHttpHandler
'Get the page stuff asynchronously.
xmlHttpRequest.Open "GET", url, True
xmlHttpRequest.send ""
Exit Function
FailedState:
msgbox Err.Number & ": " & Err.Description
End Function
Sub test()
Dim url As String, do_something As String
url = "http://httpbin.org/html"
do_something = sasynchreq(url)
'do somethign with do_something
End Sub
Everything works fine. What if I want to assign httprequest to some variable for example in test sub.?
Sounds like you're after the Property statements. In your case, it would be Property Get (https://msdn.microsoft.com/en-us/library/office/gg264197.aspx).
In your class module, put this:
Public Property Get HttpRequest() As MSXML2.XMLHTTP
Set HttpRequest = m_xmlHttp
End Property
This now gives your Module access to the class variable, like so:
Dim rq as MSXML2.XMLHTTP
set rq = MyXmlHttpHandler.HttpRequest
I think you can expect the ultimate payload predicated on the Send method be present by the time you exit your function. If so, your function signature should look something like:
Function sasynchreq(url As String) As String
and in the body of the function, there should be a:
xmlHttpRequest.send ""
sasynchreq = xmlHttpRequest.responseXML.xml
or something like that. Then do_something would be available right away for use.
Long story short, if there are no synchronicity issues, the way it's currently written, your function is not returning anything back to do_something. Whatever it is you're returning, the function signature and local variable types inside sub test should match. If you are returning an object, you will need to use the Set keyword in both instances.
Related
I am looking for way to check if files exist in a SharePoint location. I am using a function that performs this action but a message box titled: Windows Security Warning, with the message "This pages is accessing information that is not under its control. This poses a security risk. Do you want to continue?"
Here is the function I am using:
Public Function SP_File_Exists (URLString as String) as Boolean
Dim oHttPRequst as Object
if Len(Trim(URLString)) =0 Then
SP_File_Exists = False
Exit Function
Set oHttpRequest = CreateObject("MSXML2.XMLHTTP.6.0")
With oHttpRequest
.Open "GET", URLString, False
.Send
End With
If oHttpRequest.Status = 200 Or Left(oHttpRequest.Status, 1) = 3 Then ' '300 to accommodate redirects
SPFile_Exists = True
Else
SPFile_Exists = False
End If
End Function
Is there a way to suppress the message? I have tried using DisplayAlerts property and it doesn't work. I am hopeful that someone has conquered this problem.
I'm currently trying to build a collection of items wherein a collection might contain an another collection as an Item within.
I've set two collections and created a class module for each:
col1 - (linked to Class1); and col2 - (linked to Class2)
Below are my Class Modules:
Class1:
Option Explicit
Private pTestC1A As String
Private pTestC1B As Collection
Public Property Let TestC1A(Value As String)
pTestC1A = Value
End Property
Public Property Get TestC1A() As String
TestC1A = pTestC1A
End Property
Property Set TestC1B(col2 As Collection)
Set pTestC1B = col2
End Property
Property Get TestC1BElements(v As Integer) As String
TestC1B = pTestC1B(v)
End Property
Class2:
Option Explicit
Private pTestC2A As String
Public Property Let TestC2A(Value As String)
pTestC2A = Value
End Property
Public Property Get TestC2A() As String
TestC2A = pTestC2A
End Property
Below is my Module code
Sub Test()
Set col1 = New Collection
Set col2 = New Collection
Set cV = New Class1
cV.TestC1A = "First Collection"
Set aV = New Class2
aV.TestC2A = "Second Collection"
sKey1 = CStr(aV.TestC2A)
col2.Add aV, sKey1
Set cV.TestC1B = col2
sKey2 = CStr(cV.TestC1A)
col1.Add cV, sKey2
If Err.Number = 457 Then
MsgBox "Error Occured"
ElseIf Err.Number <> 0 Then Stop
End If
Err.Clear
Msgbox col1(1).TestC1A ' works fine
Msgbox col2(1).TestC2A ' works file
MsgBox col1(1).TestC1B(1).TestC2A ' does not work - 450 run-time error
End Sub
As per the above code, I'm successfully able to get the values of the items if I reference each collection respectively, however I'm getting a "Wrong number of arguments or invalid property assignment" run-time error if I try to get the item value in a nested fashion.
It would be appreciated if someone can help point out where I'm going wrong, and perhaps shed some light on the way the class module handles the Property Set & Get parameters for a collection.
You are missing a Get TestC1B property in your Class1 class module:
Property Get TestC1B() As Collection
Set TestC1B = pTestC1B
End Property
Once that is present you will be able to make calls to col1(1).TestC1B(1) and access it's .TestC2A property
Background
You did the right thing by using private variables in your classes and using properties to give read/write access to your private variables. You'll get a lot more control this way.
Property Get gives read access to that property (and broadly speaking, the underlying private variable). For example you can use Range.Address to return (read) the address of a range object.
Property Let and Set give write access to the property. Use Set for objects. For example Range.Value = 1 will write the new value to the range.
Consider, Range.Address = $A$1. Since there is no Property Set for the address of a range, this will not change the address of the range. It will consider the Range.Address part a Get call and evaluate something like $A$1 = $A$1 returning TRUE in this example
I want to import data from Anedot, a credit card processing firm, using a HTTP GET request from an MS Access program. Anedot uses a RESTful API and has provided help on there website: https://anedot.com/api/v2
I want to do this with VBA, and associate the import with a button on an MS Access form. I've read that this only possible with XML. Do I create the XML file with VBA?
I'd greatly appreciate some background information on how to get this done, as most of it is flying over my head. I don't really know where to begin and I'm having trouble finding anything useful on google.
So far I've realized I'll need to reference their API via a URL link (which they provide), and that I'll have to authorize my account using my username and a token ID. But how can I do this in VBA?
Thanks.
First of all try to make a request to API using basic authorization. Take a look at the below code as the example:
Sub Test()
' API URL from https://anedot.com/api/v2
sUrl = "https://api.anedot.com/v2/accounts"
' The username is the registered email address of your Anedot account
sUsername = "mymail#example.com"
' The password is your API token
sPassword = "1e56752e8531647d09ec8ab20c311ba928e54788"
sAuth = TextBase64Encode(sUsername & ":" & sPassword, "us-ascii") ' bXltYWlsQGV4YW1wbGUuY29tOjFlNTY3NTJlODUzMTY0N2QwOWVjOGFiMjBjMzExYmE5MjhlNTQ3ODg=
' Make the request
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", sUrl, False
.SetRequestHeader "Authorization", "Basic " & sAuth
.Send
Debug.Print .ResponseText
Debug.Print .GetAllResponseHeaders
End With
End Sub
Function TextBase64Encode(sText, sCharset) ' 05 10 2016
Dim aBinary
With CreateObject("ADODB.Stream")
.Type = 2 ' adTypeText
.Open
.Charset = sCharset ' "us-ascii" for bytes to unicode
.WriteText sText
.Position = 0
.Type = 1 ' adTypeBinary
aBinary = .Read
.Close
End With
With CreateObject("Microsoft.XMLDOM").CreateElement("objNode")
.DataType = "bin.base64"
.NodeTypedValue = aBinary
TextBase64Encode = Replace(Replace(.Text, vbCr, ""), vbLf, "")
End With
End Function
Put your credentials to sUsername and sPassword variables, choose the appropriate URL from API help page and put it to sURL. Then you can parse JSON response from the server (currently you will see the response for /v2/accounts request in Immediate window).
It's a fairly lengthy question to be honest, but lets start with some code to get you going.
This Class Module ("clsXMLHttpMonitor") should help:
Option Explicit
Dim XMLHttpReq As MSXML2.ServerXMLHTTP
Dim RequestedVar As String
Dim P As Object
Public Sub Initialize(ByVal uXMLHttpRequest As Object, Optional RequestedValue As String = "")
RequestedVar = RequestedValue
Set XMLHttpReq = uXMLHttpRequest
End Sub
Sub ReadyStateChangeHandler()
If XMLHttpReq.ReadyState = 4 Then
If XMLHttpReq.Status = 200 Then
'Process the response here
Debug.Print "200 recieved"
Set P = JSON.parse(XMLHttpReq.responseText)
Else
If XMLHttpReq.Status = 404 Then
'Handle it
End If
End If
End If
End Sub
Function returnResponseHeaders() As String
returnResponseHeaders = XMLHttpReq.getAllResponseHeaders
XMLHttpReq.Send
End Function
Function returnFullText() As String
If XMLHttpReq.ReadyState = 4 Then
If XMLHttpReq.Status = 200 Then
returnFullText = XMLHttpReq.responseText
Else
returnFullText = "-1"
End If
Else
returnFullText = ""
End If
End Function
End Function
Use it like this:
Set XMLHttpReq = New MSXML2.ServerXMLHTTP
Set XMLHttpMon = New clsXMLHttpMonitor
XMLHttpMon.Initialize XMLHttpReq
XMLHttpReq.OnReadyStateChange = XMLHttpMon
XMLHttpReq.Open "POST", URL, True
XMLHttpReq.Send strPayload
As you seem to request a Json response from a URL, you can study the Json modules here for a full implementation that collects the Json response in a collection, which you then can use in your code or save to a table. See the demo module for examples:
VBA.CVRAPI
I have a module that can be called from any part of my application to check if a particular font is installed on the users system, and if not - install the font and check again before continuing
Main Applicaton
if RequiredFont.Run(strFontName) = FALSE Then '(error message and exit sub)
Module "RequiredFont"
Public Function Run(Font As String) As Boolean
if check(Font) = FALSE Then
Run = FALSE
Install(Font)
if check(Font) = TRUE Then Run = TRUE
Else
Run = TRUE
End If
Private Function Check(Font as String) as Boolean
'code to check the font exists on the users localmachine, returns true/false
End Sub
Private Sub Install(Font as String)
'code to install the font on the users localmachine,
End Sub
My first question is:
Is the best way to make arguments available to all functions and subs, to pass them through each time I call? (as shown above).... or is there a simple way to declare an argument as variable to the whole module when Run() is called?
My Second Question is:
Is there a way I can avoid Run() alltogether and just call the module name "RequiredFont" directly, I remember that in other languages, calling a sub by a certain name would automatically run that sub when the module is called
Thank You
EDIT - This is how my code looks now:
Private FontName As String
Private FontFile As String
Public Function Run(strFontName As String, strFontFile As String) As Boolean
FontName = strFontName
FontFile = strFontFile
Run = False
If CheckFont() = False Then InstallFont
If CheckFont() = True Then
Run = True
Else
'message error"
End If
End Function
Private Function CheckFont() As Boolean
'code to check if the font is installed
On Error Resume Next
'Create a temporary StdFont object
With New StdFont
' Assign the proposed font name
.Name = FontName
' Return true if font assignment succeded
If (StrComp(FontName, .Name, vbTextCompare) = 0) = False Then
CheckFont = False
Else
CheckFont = True
End If
End With
End Function
Private Sub InstallFont()
' code to install the font
MsgBox "You need the following font installed to continue." _
& vbNewLine _
& vbNewLine & "'" & FontName & "'" _
& vbNewLine _
& vbNewLine & "Click OK to launch the font. Please click the INSTALL button at the top"
OpenFile (PATH_TO_FONTS & FontFile)
End Sub
Using function arguments is a good coding practise, that way you know exactly what goes in and what goes out a function.
You can however use a global variable, which would be set once when Run is called and still be accessible to the other functions.
'could also be Private to hide it from other modules
Public myFont As String
Public Function Run(Font As String) As Boolean
myFont = Font
'...
End Sub
Private Function Check() as Boolean
' you can access myFont here
End Sub
Private Sub Install()
'idem
End Sub
Regarding your second question, I don't think you can.
You can declare optional functions, and set defaults:
Public Function fxMyFunction _
(Optional lngProj As Variant, _
Optional strFruit As Variant = "banana", _
Optional booTest As Boolean = False) As String
'' IsMissing requires that lngProj be a Variant
booNoProject = IsMissing(lngProj)
fxMyFunction = strFruit
End Function
The Optional arguments must follow non-optional arguments.
About functions that "run on inclusion"
You do have to call functions and subs by name. There is no "self-running function" feature for a VBA standard module. VBA "includes" all modules on compiling.
VBA class modules are where you will find the equivalent of constructors. Investing in VBA's version of object-orientation doesn't seem helpful for your current need. If you do go that direction, some aspects will start looking familiar to you (though perhaps just enough to get frustrating, as OO remains a feature that was added later and looks the part).
As #z states, you can use a global variable, although this is bad practice.
Regarding question 2, you can give your function a unique name and omit naming your module to run it, e.g.
findOrInstallFont(Fontname)
My question relates to the question: JSON import to Excel
I understand most of what this post is saying, but I don't understand how to put it together in an excel module or class module. Would someone please help me to understand how I can assemble this beast to achieve my purpose?
EDIT 29-5-14 # 7.44AM -
Here is the try which I have done so far:
Installed as ClassModule syncWebRequest
'BEGIN CLASS syncWebRequest
Private Const REQUEST_COMPLETE = 4
Private m_xmlhttp As Object
Private m_response As String
Private Sub Class_Initialize()
Set m_xmlhttp = CreateObject("Microsoft.XMLHTTP")
End Sub
Private Sub Class_Terminate()
Set m_xmlhttp = Nothing
End Sub
Property Get Response() As String
Response = m_response
End Property
Property Get Status() As Long
Status = m_xmlhttp.Status
End Property
Public Sub AjaxPost(Url As String, Optional postData As String = "")
m_xmlhttp.Open "POST", Url, False
m_xmlhttp.setRequestHeader "Content-type", "application/x-www-form-urlencoded"
m_xmlhttp.setRequestHeader "Content-length", Len(postData)
m_xmlhttp.setRequestHeader "Connection", "close"
m_xmlhttp.send (postData)
If m_xmlhttp.readyState = REQUEST_COMPLETE Then
m_response = m_xmlhttp.responseText
End If
End Sub
Public Sub AjaxGet(Url As String)
m_xmlhttp.Open "GET", Url, False
m_xmlhttp.setRequestHeader "Connection", "close"
m_xmlhttp.send
If m_xmlhttp.readyState = REQUEST_COMPLETE Then
m_response = m_xmlhttp.responseText
End If
End Sub
'END CLASS syncWebRequest
Installed as Module Test
Sub Test()
Dim request As New syncWebRequest
request.AjaxGet "http://crimson/php/SiteEnquiryAjax.php?action=aisFeed&brandCode=s&siteID=0625?format=json"
Dim json As String
json = request.Response
End Sub
I'm used to working with normal modules, but have not had to work with a class module before. I am therefore trying to understand this and translate it to meet my requirements, but forgive me if I am making it a chunkier job than one would expect.
I'm trying to query data from a local webservice that returns JSON. The webservice returns data based on an ID (e.g. 0656) in the form of:
{"total":"1","results":[{"brandName":"ABC","brandingName":"Cat","siteCategory":"Production","siteName":"Scrubbed","streetAddress":"ABC RD, SUBURB, VIC 3000","siteState":"VIC","phoneNumber":"03 0909 0909","mobileNumber":"0409 090 909","faxNumber":"03 9090 9090","applicationServer":"CAT0656ABC001","dateOpened":"1979-08-21","siteNotice":"","lastContact":"2014-05-19 04:36:31",}]}
My objective is to put this in a spreadsheet where the user can enter the sites ID (e.g. 0656) and the spreadsheet will then be able to pull and respond with data as exampled above.
EDIT 29-5-14 # 8.42AM -
Updated Module Test - my intention here was to test the parser exampled in the linked question, however when I now run the macro I receive an error. My understanding is that this may be the missing piece of what I'm trying to achieve. I have now commented out the added items to stop error occurring.
Sub Test()
Dim request As New syncWebRequest
request.AjaxGet "http://crimson/php/SiteEnquiryAjax.php?action=aisFeed&brandCode=s&siteID=0625"
Dim json As String
json = request.Response
'Set clients = parser.Parse(request.Response)
'For Each client In clients
'Name = client("Name")
'State = client("siteState")
'street = client("Address")("Street")
'suburb = client("Address")("Suburb")
'city = client("Address")("City")
'Next
End Sub
EDIT 29-5-14 # 9.40AM -
To put it another way... I want to turn this: HTTP Result of "url/SiteEnquiryAjax.php?action=aisFeed&brandCode=s&siteID=0656"
{"total":"1","results":[{"brandName":"ABC","brandingName":"Cat","siteCategory":"Production","siteName":"Scrubbed","streetAddress":"ABC RD, SUBURB, VIC 3000","siteState":"VIC","phoneNumber":"03 0909 0909","mobileNumber":"0409 090 909","faxNumber":"03 9090 9090","applicationServer":"CAT0656ABC001","dateOpened":"1979-08-21","siteNotice":"","lastContact":"2014-05-19 04:36:31",}]}
Into this: Excel Output
Without knowing the exact details of the error you are receiving, I've used vba-json before and I think you're not instantiating the parser before using it:
Dim parser As New JSONLib
Set clients = parser.parse(Request.Response)
As far as the design of the project goes, I think you've done well to separate the Request as a Class and then making the request from a Module. I ran into many similar problems accessing Salesforce from Excel and created a library that I think may help: Excel-REST. It follows a similar idea, but separates it into three classes: Client, Request, and Response. Here's an example:
Sub RetrieveJSONSimple
Dim Client As New RestClient
Dim Response As RestResponse
Set Response = Client.GetJSON("url...")
If Response.Status = Ok Then
' Response.Data is parsed json
For Each Client in Response.Data
Name = Client("Name")
State = Client("siteState")
Street = Client("Address")("Street")
Suburb = Client("Address")("Suburb")
City = Client("Address")("City")
Next Client
End If
End Sub
Sub RetrieveJSONAdvanced
Dim Client As New RestClient
Client.BaseUrl = "http://crimson/php/"
' Can also setup authentication with HTTP Basic, OAuth, and others
Dim Request As New RestRequest
Request.Resource = "SiteEnquiryAjax.php?action=aisFeed&brandCode=s&siteID={SiteId}"
Request.AddUrlSegment "SiteId", "0625"
' GET and json are default, but can be set
Request.Method = httpGET
Request.Format = json
Dim Response As RestResponse
Set Response = Client.Execute(Request)
' Process as before
End Sub