When I sat down to write all the new persistence code into my UI example program I realized that while I could load data from the persister into the ManyToManyLinks instance and have those checkboxes appear checked in my listbox, I had no way of communicating the user's selection of a record back to the ManyToManyLinks instance.
There are several places in the CML_UI_ListCheckbox code where I update the queue on the user taking an action, so in each of those places I added a call to a new SetManyToManyLinkForCurrentQRecord method:
CML_UI_ListCheckbox.SetManyToManyLinkForCurrentQRecord procedure code if not self.ManyToManyLinks &= NULL if self.ListQIconField = CML_UI_ListCheckbox_TrueValue self.ManyToManyLinks.SetLinkTo(self.ListQRightRecordID) else self.ManyToManyLinks.ClearLinkTo(self.ListQRightRecordID) end end
This method looked great but it didn't compile, because while I already have a SetLinkTo method, I don't have a ClearLinkTo method. That was easy enough to fix, although for symmetry with the SetLink* methods I added two ClearLink* methods:
CML_Data_ManyToManyLinks.ClearLinkTo procedure(long rightRecordID) code self.ClearLinkBetween(self.LeftRecordID,rightRecordID) CML_Data_ManyToManyLinks.ClearLinkBetween procedure(long leftRecordID,long rightRecordID) code clear(self.LinksData.LinksQ) self.LinksData.LinksQ.LeftRecordID = LeftRecordID self.LinksData.LinksQ.RightRecordID = rightRecordID get(self.LinksData.LinksQ,self.LinksData.LinksQ.LeftRecordID,self.LinksData.LinksQ.RightRecordID) if not errorcode() then delete(self.linksData.LinksQ).
But I had another problem: I didn't have a self.ListQRightRecordID property, because none of my UI code had had to deal with this requirement before. That meant adding an ID field to the queue:
CheckboxQueue queue Checked bool CheckedIcon long CheckedText string(30) ID long end
and adding a property to the CML_UI_ListCheckbox class and changing the Initialize prototype:
CML_UI_ListCheckbox Class,Type,Module('CML_UI_ListCheckbox.CLW'),Link('CML_UI_ListCheckbox.CLW',_CML_Classes_LinkMode_),Dll(_CML_Classes_DllMode_) ToggleAndAdvanceWithSpaceKey bool ListFEQ long ListQ &queue ListQCheckboxFieldNumber long ListQIconField &long ListQRightRecordID &long ... Initialize procedure(*Queue listQ,*long listQIconField,*long listQRightRecordID, long listFEQ,long listQCheckboxFieldNumber=1,CML_Data_ManyToManyLinks ManyToManyLinks) ...
and then calling the Initialize method, passing the new field:
ListCheckbox.Initialize(CheckboxQueue,CheckboxQueue.CheckedIcon,CheckboxQueue.ID,?List,,links)
With these changes I could run my UITest program and have my checkbox settings loaded on startup and saved on exit with the Links.Load() and Links.Save() method calls.
Cleaning up the code
But there were still a few things that bothered me about the code. For one, while I'd created a class to maintain the queue of record associations, the class really didn't add any value. It was simply a container for the class. So I got rid of the class and declared the queue type in CML_Data_ManyToManyLinksDataQ.inc:
CML_Data_ManyToManyLinksDataQ queue,TYPE LeftRecordID long RightRecordID long end
Everywhere I had previously used the class and the queue reference, I now used just the queue reference. That took a bit of searching and replacing, and when I was done I ran my unit tests again to make sure I hadn't messed anything up.
Storing data efficiently
Another thing I didn't like about the class was the brute force approach to saving data. If I had fifty linking records and I removed one of them, why not just remove that single record instead of wiping out all the records and recreating all but one?
To do that I needed to change CML_Data_ManyToManyLinksDataQ so I could track the state of a link in the list box as well as the presence of a link in the database:
CML_Data_ManyToManyLinksDataQ queue,TYPE LeftRecordID long RightRecordID long IsLinked bool IsPersisted bool end
That meant my ClearLinkBetween method could no longer assume the mere presence of a linking record. I changed the code to this:
CML_Data_ManyToManyLinks.ClearLinkBetween procedure(long leftRecordID,long rightRecordID) code if self.GetLinkRecord(leftRecordID,rightRecordID) self.LinksDataQ.IsLinked = FALSE put(self.LinksDataQ) end
Similarly I changed IsLinkBetween to:
CML_Data_ManyToManyLinks.IsLinkBetween procedure(long leftRecordID,long rightRecordID)!,bool code if self.GetLinkRecord(LeftRecordID,RightRecordID) if self.LinksDataQ.IsLinked then return true. end return false
and SetLinkBetween to:
CML_Data_ManyToManyLinks.SetLinkBetween procedure(long leftRecordID,long rightRecordID) code if not self.GetLinkRecord(LeftRecordID,RightRecordID) clear(self.LinksDataQ) self.LinksDataQ.LeftRecordID = LeftRecordID self.LinksDataQ.RightRecordID = rightRecordID self.LinksDataQ.IsLinked = true add(self.LinksDataQ) else self.LinksDataQ.IsLinked = true put(self.LinksDataQ) end
Both methods need to look for an existing queue record, which is why I extracted that code into a GetLinkRecord method:
CML_Data_ManyToManyLinks.GetLinkRecord procedure(long leftRecordID,long rightRecordID)!,bool code clear(self.LinksDataQ) self.LinksDataQ.LeftRecordID = LeftRecordID self.LinksDataQ.RightRecordID = rightRecordID get(self.LinksDataQ,self.LinksDataQ.LeftRecordID,self.LinksDataQ.RightRecordID) if not errorcode() then return true. return false
The CML_Data_ManyToManyLinksPersister's Load method remains the same, but the Save method looks quite a bit different:
CML_Data_ManyToManyLinksPersister.Save procedure(long leftRecordID,CML_Data_ManyToManyLinksDataQ linksDataQ) x long code if self.OpenDataFile() dbg.write('CML_Data_ManyToManyLinksPersister.Save') loop x = 1 to records(linksDataQ) get(LinksDataQ,x) if LinksDataQ.IsLinked and not LinksDataQ.IsPersisted self.AddLinkRecord(LinksDataQ.LeftRecordID,LinksDataQ.RightRecordID) LinksDataQ.IsPersisted = true put(linksDataQ) elsif not LinksDataQ.IsLinked and LinksDataQ.IsPersisted self.RemoveLinkRecord(LinksDataQ.LeftRecordID,LinksDataQ.RightRecordID) LinksDataQ.IsPersisted = false put(linksDataQ) end end self.CloseDataFile() end
This code loops through the queue records and if a record is linked but not persisted it adds a linking record, and if a record is not linked but is persisted it removes the linking record. It also sets the IsPersisted flag to the current state as appropriate so the next time around the Save call will not attempt to re-save or re-remove a record.
The AddLinkRecord and RemoveLinkRecord methods are virtuals, and are just stubs in CML_Data_ManyToManyLinksPersister. They are implemented in CML_Data_ManyToManyLinksPersisterForTPS:
CML_Data_ManyToManyLinksPersisterForTPS.AddLinkRecord procedure(long leftRecordID,long rightRecordID)!,derived code dbg.write('AddLinkRecord(' & leftRecordID & ',' & rightRecordID & ')') clear(Links:Record) Links:LeftRecordID = LeftRecordID Links:RightRecordID = rightRecordID add(LinksDataFile) if errorcode() dbg.write('Error adding record: ' & error()) else dbg.write('Success adding record') end CML_Data_ManyToManyLinksPersisterForTPS.RemoveLinkRecord procedure(long leftRecordID,long rightRecordID)!,derived code dbg.write('RemoveLinkRecord(' & leftRecordID & ',' & rightRecordID & ')') clear(Links:Record) Links:LeftRecordID = LeftRecordID Links:RightRecordID = rightRecordID get(LinksDataFile,Links:kLeftRight) if not errorcode() dbg.write('deleting record') delete(LinksDataFile) end
There's still work to be done here, not least of which is adding some measure of error handling. But this code does work. The unit tests pass, and in my UITest program my checkbox settings are preserved between runs.