Class object within Dictionary member variable of Class (1 Viewer)

nhorton79

Registered User.
Local time
Today, 17:58
Joined
Aug 17, 2015
Messages
147
Hi All,

I have a class 'LineItem':
Code:
Option Explicit

' Member variables

Private m_LineItemID As String
Private m_Description As String
Private m_Quantity As Double
Private m_UnitAmount As Double
Private m_AccountCode As String
Private m_TaxType As String


' Properties

Property Get LineItemID() As String
    LineItemID = m_LineItemID
End Property

Property Let LineItemID(value As String)
    m_LineItemID = value
End Property

Property Get Description() As String
    Description = m_Description
End Property

Property Let Description(value As String)
    m_Description = value
End Property

'...Balance of Property declarations for other member variables

Property Get TaxType() As String
    TaxType = m_TaxType
End Property

Property Let TaxType(value As String)
    m_TaxType = value
End Property


' Methods
Public Sub Init(Description As String)
    Me.Description = Description
End Sub

Then I have another class called 'Invoice'
Code:
Option Explicit

' Member variables

Private m_InvoiceID As String
Private m_InvoiceNumber As String
Private m_Reference As String
Private m_ContactID As String
Private m_InvoiceDate As Date
Private m_DueDate As Date
Private m_BrandingTheme As String
Private m_LineItems As Dictionary


' Properties

Property Get InvoiceID() As String
    InvoiceID = m_InvoiceID
End Property

Property Let InvoiceID(value As String)
    m_InvoiceID = value
End Property

Property Get InvoiceNumber() As String
    InvoiceNumber = m_InvoiceNumber
End Property

Property Let InvoiceNumber(value As String)
    m_InvoiceNumber = value
End Property

'...Balance of Property declarations for other member variables

Property Get BrandingTheme() As String
    BrandingTheme = m_BrandingTheme
End Property

Property Let BrandingTheme(value As String)
    m_BrandingTheme = value
End Property


Property Get LineItems(key As Integer) As LineItem
    Set LineItems(key) = m_LineItems(key)
End Property

Property Set LineItems(key As Integer, value As LineItem)
    m_LineItems.Add key, value
End Property


' Methods

Public Sub Class_Initialize()
    Set m_LineItems = New Dictionary
End Sub

Public Sub Init(ContactID As String, InvoiceNumber As String, Reference As String, InvoiceDate As Date, DueDate As Date)
    Me.ContactID = ContactID
    Me.InvoiceNumber = InvoiceNumber
    Me.Reference = Reference
    Me.InvoiceDate = InvoiceDate
    Me.DueDate = DueDate
    Me.BrandingTheme = BrandingTheme
End Sub

I'm trying to create an instance of an Invoice and then create multiple instances of LineItem and store them in the LineItems dictionary.
I'm testing in a Public Sub:

Code:
Public Sub InvoiceTest()

Dim INV As New Invoice
Dim LI1 As New LineItem

LI1.Init "Design, produce and install building signage to the new premises as requested by John"
LI1.Quantity = 1
LI1.UnitAmount = 2250

INV.Init "", "39799", "John", #8/10/2020#, #10/30/2020#
INV.LineItems.Add 1, LI1

End Sub

I keep getting error "Argument is not optional" on the line:
Code:
INV.LineItems.Add 1, LI1

I've tried changing declaration of the m_LineItems as many different types, but cannot seem to get this to work, and would really appreciate any assistance.
 

MajP

You've got your good things, and you've got mine.
Local time
Today, 01:58
Joined
May 21, 2018
Messages
8,527
This setter makes no sense
Code:
Property Set LineItems(key As Integer, value As LineItem)
    m_LineItems.Add key, value
End Propert

When making a custom "collection" or using a "collection" in a composite class, you need some add methods. Here is an example of a Paths Class that holds paths. Each path has a start city, end city, and distance (not important). The example provides multiple ways to add a path to Paths. You can create the path object and pass it in, or pass in the properties of a path and create and add at the same time.
In this construct you normally will have an Add, Item, Count, and Delete property/method. When working with a dictionary I often have an ItemByKey and ItemByIndex.

Code:
Option Compare Database
Option Explicit
'need reference to MicroSoft Scripting runtime
Private mPaths As New Dictionary
Public Property Get count() As Long
  count = mPaths.count
End Property
Public Function AddPath(StartCity As City, endCity As City, distance As Double) As Path
  Dim newPath As New Path
  Set newPath.StartCity = StartCity
  Set newPath.endCity = endCity
  newPath.distance = distance 'may not be necessary
  mPaths.Add StartCity.Cityname & "-" & endCity.Cityname, newPath
  Set AddPath = newPath
End Function
Public Function AddPath2(StartCityName As String, EndCityName As String, distance As Double) As Path
  Dim newPath As New Path
  Dim StartCity As New City
  Dim endCity As New City
  StartCity.Cityname = StartCityName
  endCity.Cityname = EndCityName
  Set newPath.StartCity = StartCity
  Set newPath.endCity = endCity
  newPath.distance = distance 'may not be necessary
  mPaths.Add StartCity.Cityname & "-" & endCity.Cityname, newPath
  Set AddPath2 = newPath
End Function
Public Function Add(ThePath As Path) As Path
  mPaths.Add ThePath.StartCity.Cityname & "-" & ThePath.endCity.Cityname, ThePath
  Set Add = ThePath
End Function
Public Property Get ItemByIndex(Index As Variant) As Path
  Index = CLng(Index)
  Set ItemByIndex = mPaths.Item(mPaths.Keys(Index))
End Property
Public Property Get Item(Key As Variant) As Path
  Set Item = mPaths.Item(Key)
End Property
Public Sub DeletePath(Key As Variant)
mPaths.Remove Key
End Sub
Public Function Paths() As Dictionary
  'This is a workaround since you cannot overload
      Set Paths = mPaths
End Function

In this example I have 3 add methods. In your case you could have 2
To simplify I would have one were you can pass in the key, description and optionally any other property. This will make and add the line item.
In the other you pass in the key and line item that you created. Similar to what you are doing now.
 
Last edited:

nhorton79

Registered User.
Local time
Today, 17:58
Joined
Aug 17, 2015
Messages
147
I’m wanting to use this in a POST request to an API using VBA-Web and VBA-Json.

I need to have multiple LineItems to post to XeroAPI.

I’d tried using a UDT for the LineItems but vba didn’t like these in the class.
 

Galaxiom

Super Moderator
Staff member
Local time
Today, 15:58
Joined
Jan 20, 2009
Messages
12,852
IIRC the key to a Dictionary is a String.
 

arnelgp

..forever waiting... waiting for jellybean!
Local time
Today, 13:58
Joined
May 7, 2009
Messages
19,233
Why make it so hard when you can put those on two Tables (header/detail).
 

nhorton79

Registered User.
Local time
Today, 17:58
Joined
Aug 17, 2015
Messages
147
Thanks MajP!

I had been thinking to use the internal methods with the dictionary, but better as your method shows to create my own methods to add, remove etc.

I’ve got it working now.

I could post final code but took directly from MajP’s examples and just changed a few names for dictionary and methods.
 

MajP

You've got your good things, and you've got mine.
Local time
Today, 01:58
Joined
May 21, 2018
Messages
8,527
This is how I do it

LineItem
Code:
Option Explicit

' Member variables

Private m_LineItemID As String
Private m_Description As String
Private m_Quantity As Double
Private m_UnitAmount As Double
Private m_sAccountCode As String
Private m_TaxType As String

' Properties
Property Get LineItemID() As String
    LineItemID = m_LineItemID
End Property
Property Let LineItemID(value As String)
    m_LineItemID = value
End Property
Property Get Description() As String
    Description = m_Description
End Property
Property Let Description(value As String)
    m_Description = value
End Property
'...Balance of Property declarations for other member variables
Property Get TaxType() As String
    TaxType = m_TaxType
End Property
Property Let TaxType(value As String)
    m_TaxType = value
End Property
Public Property Get Quantity() As Double
    Quantity = m_Quantity
End Property

Public Property Let Quantity(ByVal NewValue As Double)
    m_Quantity = NewValue
End Property

Public Property Get UnitAmount() As Double
    UnitAmount = m_UnitAmount
End Property

Public Property Let UnitAmount(ByVal NewValue As Double)
    m_UnitAmount = NewValue
End Property

Public Property Get AccountCode() As String
    AccountCode = m_sAccountCode
End Property

Public Property Let AccountCode(ByVal sNewValue As String)
    m_sAccountCode = sNewValue
End Property

' Methods
Public Sub Init(LineItemID As String, Description As String, Optional TaxType As String = "TBD")
    Me.LineItemID = LineItemID
    Me.Description = Description
    Me.TaxType = TaxType
End Sub

Public Function ToString() As String
    ToString = "Line Item ID: " & Me.LineItemID & " Desc: " & Me.Description & " TaxType: " & Me.TaxType
End Function

LineItems
Code:
Option Compare Database
Option Explicit

Private m_LineItems As Scripting.Dictionary
'need reference to MicroSoft Scripting runtime

Public Property Get count() As Long
  count = m_LineItems.count
End Property

'Pass in a line item
Public Function Add(TheLineItem As LineItem) As LineItem
  m_LineItems.Add TheLineItem.LineItemID, TheLineItem
  Set Add = TheLineItem
End Function
'Pass in by properties

Public Function Add2(LineItemID As String, Description As String, Optional TaxType As String = "TBD") As LineItem
  Dim newLineItem As New LineItem
  If Not LineItemExists(LineItemID) Then
    newLineItem.Init LineItemID, Description, TaxType
    m_LineItems.Add LineItemID, newLineItem
    Set Add2 = newLineItem
  Else
    MsgBox "LineItemId: " & LineItemID & " already exists"
  End If
End Function

Public Property Get ItemByIndex(Index As Variant) As LineItem
  Index = CLng(Index)
  Set ItemByIndex = m_LineItems.Item(m_LineItems.keys(Index))
End Property

Public Property Get ItemByKey(key As Variant) As LineItem
  Set ItemByKey = m_LineItems.Item(key)
End Property

Public Sub DeleteLineItem(key As Variant)
  m_LineItems.Remove key
End Sub

Public Function LineItems() As Dictionary
  'This is a workaround since you cannot overload
      Set LineItems = m_LineItems
End Function

Public Function LineItemExists(LineItemID As String) As Boolean
  LineItemExists = LineItems.Exists(LineItemID)
End Function

Public Function ToString() As String
  Dim Thekeys As Variant
  Dim i As Integer
  Dim LI As LineItem
  Thekeys = LineItems.keys
  For i = 0 To UBound(Thekeys)
    Set LI = m_LineItems(Thekeys(i))
    If ToString = "" Then
      ToString = LI.ToString
    Else
      ToString = ToString & vbCrLf & LI.ToString
    End If
  Next i
End Function

Private Sub Class_Initialize()
  Set m_LineItems = New Dictionary
End Sub

Testing all the features add, delete, item, tostring
Code:
Public Sub TestLineItem()
  Dim LIS As New LineItems
  Dim LI As New LineItem
 
  'Add method 1
  LI.Init "ABC", "Line item DEF", "Corporate"
  LIS.Add LI
  'Add method 2
  LIS.Add2 "DEF", "Line Item ABC"
  'Verify data
  Debug.Print LIS.ToString
  'Show by key and index
  Debug.Print "By Index 1 " & LIS.ItemByIndex(1).ToString
  Debug.Print "By Key DEF " & LIS.ItemByKey("DEF").ToString
 
  'Add duplicate
  LIS.Add2 "ABC", "this is a duplicate"
 
  'Remove
  Debug.Print LIS.count
  LIS.DeleteLineItem "ABC"
  'Verify
  Debug.Print LIS.count
End Sub
Results
Code:
Line Item ID: ABC Desc: Line item DEF TaxType: Corporate
Line Item ID: DEF Desc: Line Item ABC TaxType: TBD
By Index 1 Line Item ID: DEF Desc: Line Item ABC TaxType: TBD
By Key DEF Line Item ID: DEF Desc: Line Item ABC TaxType: TBD
 2
 1

Complete
Code:
Public Sub InvoiceTest()
 Dim INV As New Invoice
 Dim LI1 As New LineItem
 
 INV.Init "", "39799", "John", #8/10/2020#, #10/30/2020#
 
 
 LI1.Init "ABC", "Design, produce and install building signage to the new premises as requested by John"
 LI1.Quantity = 1
 LI1.UnitAmount = 2250
 
 INV.LineItems.Add LI1
 Debug.Print INV.LineItems.ToString
 INV.LineItems.Add2 "DEF", "Design blah", "Corporate"
 Debug.Print INV.LineItems.ToString
End Sub
 

MajP

You've got your good things, and you've got mine.
Local time
Today, 01:58
Joined
May 21, 2018
Messages
8,527
I assume that you ItemID is unique and it will be the key. Notice that I verify it is unique before adding.
 

MajP

You've got your good things, and you've got mine.
Local time
Today, 01:58
Joined
May 21, 2018
Messages
8,527
I had been thinking to use the internal methods with the dictionary,
You can do that and not roll your own custom collection, but you can do much more if you encapsulate it into a custom "collection". You can add multiple add methods, verify your keys, add a tostring method etc. Then it becomes much more seamless and user friendly.
 

Users who are viewing this thread

Top Bottom