by
...
Unknown user
ClarionMag reader Greg Fasolt recently contacted me with a question about a class I wrote some years ago, and which can be found at http://archive.clarionmag.com/cmag/v4/v4n06checkbox2.html. That class makes it easy (or at least easier) to manage many to many links between two browses by way of checkboxes, like this:
...
Greg was having some issues trying to use the class with a list box on a form, and he was also interested in some functionality the class didn't support.
Ye olde code
Revisiting code is almost always an instructional, if not always pleasant, experience.
...
I might or might not want to keep this code as ABC-compatible, but not being able to compile was a showstopper.
Loading custom ABC classes
The Clarion IDE only scans certain directories for ABC-compatible classes, which is why it couldn't find my class; these settings are under Tools | Options | Clarion | Clarion for Windows | Versions.
I could put my class files in the Accessory\libsrc\win directory, but I think some major changes may be in the offing, and this class may or may not be ABC when I'm done. So I wiped my watery eyes, held my nose and added my current application directory to the list.
...
With that done I tried generating and compiling and got the same errors as before. Why? Because the IDE hadn't refreshed its list of classes. This can be forced from Global Properties | Actions, but from old habit I closed and opened the app and that did the job too.
I ran the app and verified that everything worked.
Source code
Here's the class header, in all its glory:
...
Code Block |
---|
MEMBER omit('***',_c55_) _ABCDllMode_ EQUATE(0) _ABCLinkMode_ EQUATE(1) *** MAP . INCLUDE('ccibrowb.inc') include('keycodes.clw') dataQ queue,type ID long CurrValue long Changed byte end cciBrowseClassB.RedisplayRecord PROCEDURE() ChangeIt byte(ChangeRecord) Finished byte(RequestCompleted) code self.ResetFromAsk(ChangeIt,Finished) cciBrowseClassB.Init procedure(cciBrowseClassBparams paramsB) code self.Init( | paramsB.LinkFM, | paramsB.LinkKey, | paramsB.LinkLeftField, | ! paramsB.LinkLeftFieldName | paramsB.LinkRightField, | paramsB.LeftPrimaryID, | paramsB.RightPrimaryID, | paramsB.ViewQ, | paramsB.ViewQIconField, | paramsB.ViewQRightField) !string LinkLeftFieldName, cciBrowseClassB.Init procedure(FileManager LinkFM,Key LinkKey,| *long LinkLeftField,*long LinkRightField,| *long LeftPrimaryID,*long RightPrimaryID,| Queue ViewQ,*? ViewQIconField,*long ViewQRightField) ListControl long !FileManager LinkFM ! File manager for linking file !Key LinkKey ! Primary key for linking file !*long LinkLeftField ! Linking file left field !*long LinkRightField ! Linking file right field !*long LeftPrimaryID ! Left file primary key field !*long RightPrimaryID ! Right file primary key field !any ViewQIconField ! local for checkbox display code self.Debug = true self.FileLoaded = 1 self.LinkFM &= LinkFM self.LinkKey &= LinkKey self.RightPrimaryID &= RightPrimaryID self.LeftPrimaryID &= LeftPrimaryID self.LinkLeftField &= LinkLeftField !self.LinkLeftFieldName = clip(LinkLeftFieldName) self.LinkRightField &= LinkRightField self.ViewQRightField &= ViewQRightField self.DataQ &= new DataQ self.ViewQIconField &= ViewQIconField self.ViewQ &= ViewQ !self.LeftValue = 0 self.RetainRow = 1 compile('***',_c55_) self.lc = self.ilc.getControl() *** omit('***',_c55_) self.lc = self.ListControl *** cciBrowseClassB.Kill procedure code self.ViewQIconField &= null free(self.DataQ) dispose(self.DataQ) parent.kill() cciBrowseClassB.DebugMsg procedure(String msg) code if self.Debug and ~(self.DebugQ &= null) self.DebugQ = msg add(self.DebugQ) end cciBrowseClassB.LoadCheckboxData procedure x long code self.DebugMsg('called LoadCheckboxData with leftPrimaryID ' & self.LeftPrimaryID) free(self.DataQ) self.LinkRightField = 0 self.LinkLeftField = self.LeftPrimaryID set(self.LinkKey,self.LinkKey) loop while self.LinkFM.next() = level:benign if self.LinkLeftField <> self.LeftPrimaryID break end self.DataQ.ID = self.LinkRightField self.DataQ.CurrValue = 1 self.DataQ.Changed = false add(self.DataQ,self.DataQ.ID) self.DebugMsg('Added dataq record for LinkRightField ' & self.LinkRightField) end self.DebugMsg(records(self.dataq) & ' records loaded by LoadCheckboxData') self.SetIcons() cciBrowseClassB.SaveCheckboxData procedure select cstring(500) x long code self.DebugMsg('called SaveCheckboxData') loop x = 1 to records(self.DataQ) get(self.DataQ,x) if (self.DataQ.Changed) self.debugmsg('element ' & x & ' has changed, value is now ' & self.dataq.currvalue) if self.DataQ.CurrValue = 0 self.debugmsg('DELETING') ! If the link exists, remove it self.LinkLeftField = self.LeftPrimaryID self.LinkRightField = self.RightPrimaryID !self.DebugMsg(self.LinkLeftField & '/' & self.LinkRightField & '') if self.LinkFM.Fetch(self.LinkKey) = level:benign self.debugmsg('item found, calling deleterecord') !self.DebugMsg('** deleted *** (' & self.ViewQIconField & '/' & self.RightPrimaryID & ')') compile('***',_c55_) self.linkFM.DeleteRecord(0) *** omit('***',_c55_) delete(self.LinkFM.File) *** end else self.debugmsg('INSERTING') ! Create the link self.LinkLeftField = self.LeftPrimaryID self.LinkRightField = self.RightPrimaryID self.LinkFM.TryInsert() self.DebugMsg('** added *** (' & self.ViewQIconField & '/' & self.RightPrimaryID & ')') end self.DataQ.Changed = false put(self.DataQ) else self.debugmsg('element ' & x & ' has NOT changed, value is ' & self.dataq.currvalue) end end cciBrowseClassB.SetIcons PROCEDURE() x long code self.DebugMsg('SetIcons (' & records(self.viewq) & ')') loop x = 1 to records(self.ViewQ) !self.DebugMsg('Getting viewq record ' & x) get(self.ViewQ,x) self.DataQ.ID = self.ViewQRightField self.DebugMsg('got self.dataq.id ' & self.dataq.id) get(self.DataQ,self.DataQ.ID) if errorcode() !self.DebugMsg('SetIcons did not find record in DataQ for primary ' & self.ViewQRightField & ', set ViewQIconField to 1') self.ViewQIconField = 1 else self.ViewQIconField = choose(self.DataQ.CurrValue=1,2,1) self.DebugMsg('SetIcons found record in DataQ for primary ' & self.ViewQRightField | & ', set ViewQIconField to ' & self.ViewQIconField) end put(self.ViewQ) end cciBrowseClassB.TakeEvent PROCEDURE code parent.TakeEvent() self.DebugMsg('field ' & field() & ', event ' & event() & ', keycode ' & keycode()) if field() = self.lc if event() = event:Accepted | and keycode() = MouseLeft | and self.lc{proplist:mouseuprow} = self.lc{proplist:mousedownrow} | and self.lc{proplist:mouseupfield} = self.lc{proplist:mousedownfield} | and self.lc{proplist:mousedownfield} = 1 self.debugmsg('clicked on checkbox') ! ! Get the current record ! self.UpdateViewRecord() ! ! Update the buffer ! self.UpdateBuffer() get(self.ViewQ,choice(self.lc)) self.DataQ.ID = self.ViewQRightField get(self.DataQ,self.DataQ.ID) if errorcode() !self.debugmsg('no dataq record for id ' & self.ViewQRightField & ', adding now') self.DataQ.ID = self.RightPrimaryID self.DataQ.CurrValue = 1 self.DataQ.Changed = true add(self.DataQ,self.DataQ.ID) else !self.debugmsg('found dataq record for id ' & self.ViewQRightField & ', value was ' & self.DataQ.CurrValue) self.DataQ.CurrValue = choose(self.DataQ.CurrValue = 1,0,1) self.debugmsg('setting to ' & self.DataQ.CurrValue) self.DataQ.Changed = true put(self.DataQ) end self.setIcons() self.SaveCheckboxData() end end |
Reconsidering the design
Aside from the lack of a consistent naming convention, this class has other problems. For one thing it combines user interface and database code, and while this is common practice in the Clarion world it does make class reuse and repurposing more difficult. What if you want to use a simple list box instead of an ABC browse? Or instead of saving changes to disk each time the user clicks a checkbox, save to disk once the user moves on to a different record in the left hand browse (perhaps with a confirm message)? And what if you want to save to some data store other than a file, say via a web service?
...
And too many times I've looked at some code and thought "this easy to fix, no need to break it up into a set of classes" only to have the complexity grow over time to where I had to refactor anyway. By then I've typically invested wasted many hours keeping the code limping along.