Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

I don't think I've ever written a ten part series before, but then again there aren't that many topics in Clarion programming that catch my interest quite as much as moving business logic out of embed points and into testable, reusable, maintainable code.

To be sure, this code already was a class in its first iteration, but another subject that's close to my heart is refactoring code to make it more testable, reusable and maintainable. And the principle is still the same - take that business logic and and break it up into as many classes as makes sense. For simple logic, often a single class will do. But more often than not I find even code that looks fairly simple at the start can easily be broken down into two or more classes. 

A code walkthrough

I've already talked in some detail about the individual classes, so in this installment I'll focus on the flow of code through the program. I've also included a download of all the source you need to build the sample program for yourself (just in case you don't want to get it from the ClarionMagLibrary Git repository).

Here's the running program:

Image Added

And here's the full source for that program (minus the classes, which I'll get to shortly):

Code Block
                                                PROGRAM
                                                MAP
                                                END
    include('CML_UI_ListCheckbox.inc'),once
    include('CML_Data_ManyToManyLinks.inc'),once
    include('CML_Data_ManyToManyLinksPersisterForTPS.inc'),once
    include('CML_System_Diagnostics_Logger.inc'),once
    include('keycodes.clw'),once
dbg                                             CML_System_Diagnostics_Logger
x                                               long
CheckboxQueue                                   queue
Checked                                             bool
CheckedIcon                                         long
CheckedText                                         string(30)
ID                                                  long
                                                end
ListCheckbox                                    CML_UI_ListCheckbox
Links                                           CML_Data_ManyToManyLinks
TpsPersister                                    CML_Data_ManyToManyLinksPersisterForTPS
WINDOW                                          WINDOW('UITest'),AT(,,179,247),GRAY,FONT('Microsoft Sans Serif',8)
                                                    LIST,AT(9,10,161,185),USE(?List),VSCROLL,FROM(CheckboxQueue), |
                                                        FORMAT('14L(2)|M~X~100L(2)|M~Text~')
                                                    BUTTON('Check All'),AT(8,199,47,14),USE(?ButtonCheckAll)
                                                    BUTTON('Uncheck All'),AT(66,199,47,14),USE(?ButtonUncheckAll)
                                                    BUTTON('Close'),AT(124,226,47),USE(?ButtonClose),STD(STD:Close)
                                                    BUTTON('Toggle All'),AT(124,199,47,14),USE(?ButtonToggleAll)
                                                END
    CODE
    loop x = 1 to 100
        clear(CheckboxQueue)
        CheckboxQueue.CheckedText = 'String ' & x
        CheckboxQueue.ID = x
        add(CheckboxQueue)
    end
    open(window)
    TpsPersister.SetFilename('LinksData')
    Links.Persister &= TpsPersister
    Links.LeftRecordID = 1
    Links.Load()
    ListCheckbox.ToggleAndAdvanceWithSpaceKey = true
    ListCheckbox.Initialize(CheckboxQueue,CheckboxQueue.CheckedIcon,CheckboxQueue.ID,?List,,links)
    ListCheckbox.LoadCheckboxData()
    accept
        case accepted()
        of ?ButtonCheckAll
            ListCheckbox.CheckAll()
        of ?ButtonUncheckAll
            ListCheckbox.UncheckAll()
        of ?ButtonToggleAll
            ListCheckbox.ToggleAll()
        end
    end
    close(window)
    Links.Save()

Let's walk through the code. 

First up, the queue declaration:

Code Block
CheckboxQueue                                   queue
Checked                                             bool
CheckedIcon                                         long
CheckedText                                         string(30)
ID                                                  long
                                                end

The first field in the queue is a BOOL (which is actually SIGNED, which is actually LONG, but the idea is it's a true/false flag). This field is not actually displayed; instead an icon is displayed because the list format string says that the first field is really an icon, and when a field is an icon the field immediately following it needs to be a long which indicates which icon will be displayed, in this case a checked checkbox or an unchecked checkbox. More on how that works in a moment. 

The third field in the queue is really the second field you see in the actual list box - the descriptive text. 

The last field is not displayed, and it contains the ID of the "right side" record. The idea is you have one record in memory (the "left side" record) and you want to know to which "right side" records this one record is linked. For example in the case of a student who is attending a number of classes, the student ID is the left side ID, and the class ID is the right side ID. The idea of which is left and which is right is somewhat arbitrary. 

The code next declares some object instances:

Code Block
ListCheckbox                                    CML_UI_ListCheckbox
Links                                           CML_Data_ManyToManyLinks
TpsPersister                                    CML_Data_ManyToManyLinksPersisterForTPS

If I wanted to create these instances at runtime I would declare them as reference variables and then add statements in the code to create new instances and assign them to those references. I would also have to dispose of the references when done to avoid memory leaks. In fact when I'm working with classes I find I create instances on the fly far more often than I declare them as in this example. But if you can get away with it, letting the compiler create (and dispose) your classes does make life a little simpler, and avoids the problem of accidentally referencing a null reference variable and crashing the program. 

There's a bit of code at the beginning of the procedure that loads 100 records into the queue so there's some data to play with. At this point none of the records is marked as checked. In the original version of this UITest program I did randomly set some checked values, but that isn't necessary now as any previously checked values will be loaded up when the program runs. 

Next I open the window, then