Versions Compared

Key

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

I've written a few templates over the years. Not as many as template mavens like Mike Hanson and Lee White, but I've touched just about every aspect of the template language at one time or another. 

And more often than not I find myself peering into a dark place somewhere in the bowels of the template engine, wondering what gyrations I need to go through to get the AppGen to spit out the code I want. That's not because the template system is bad; it's just very powerful and very complex. 

This complexity is one of the reasons I rarely start with a template. I almost always write code to solve the problem, and then figure out how to get the AppGen to generate the exact same code. Debugging source code can be tricky; debugging template code can be trickier; doing both at the same time is just plain dumb. 

I had already created a browse procedure with embedded source that worked. In preparation for the template I made a copy of that procedure and the ripped out all of the embedded source. 

I'm a huge fan of Scooter Software's Beyond Compare, and have been for years. As I almost always generate each procedure into its own source module, it only took a moment to set up a comparison of the source for the two procedures. 

I decided the simplest way to implement the template was as an extension (although down the road a control template would be a nice addition).

Here's the declaration, the very beginning of the extension template:

Code Block
#Extension(ManyToManyCheckboxForABCBrowse,'Many-to-many checkboxes for an ABC browse'),REQ(BrowseBox(ABC)),PROCEDURE

There are two important attributes on this extension: it's a PROCEDURE level extension, and it also needs to be attached to an instance of the ABC BrowseBox template. Originally I also had the WINDOW attribute on the extension, out of habit, but it seemed redundant since there would have to be a window in order to populate the browse box. Something made me check the help for #EXTENSION and I was surprised to see that the WINDOW attribute isn't even documented. 

Because the extension requires the ABC BrowseBox template, you have to be on the extensions tab and select a browse box before you can add this extension.

Image Added

The prompts

The extension has the following prompts. If you've read the previous installments you'll be familiar with the "left" and "right" terminology for the browses involved. There's also a checkbox for when the left side is a browse (the default); I'll get to that in a little bit. 

Image Added

First up is the data section declaration of the local class that does most of the work:

Code Block
#At(%DataSection),Priority(3100)
%[25]M2MClassInstanceName  class
ListCheckbox                   &CML_UI_ListCheckbox
Persister                      &CML_Data_ManyToManyLinksPersisterForABC
Links                          &CML_Data_ManyToManyLinks
Construct                      procedure
Destruct                       procedure
DisplayCheckboxData            procedure
Init                           procedure
LoadEnrollmentData             procedure
SaveEnrollmentData             procedure
SetCheckboxIcon                procedure
                           end                   
#EndAt

I've used the %[offset] technique that pads the contents of %M2MClassInstanceName to 25 characters so my class declaration formats nicely. Of course I'm persisting in using four space indents, so it won't look quite the same as generated code, but then the generated is wildly inconsistent anyway. 

The next #At section embeds code in the window manager's Init method:

Code Block
#At(%WindowManagerMethodCodeSection,'Init','(),BYTE'),PRIORITY(8005),DESCRIPTION('Initialize M2MCheckboxList')
%M2MClassInstanceName.Init()
    #If(not %LeftFileIsInBrowse)
%M2MClassInstanceName.LoadEnrollmentData()  
    #EndIf
#EndAt

This is where that checkbox comes into play. Greg Fasolt, who has been alpha testing the classes and the template, told me he had a number of forms where he wanted to use this template. In those situations there is no left side browse, which actually makes things a bit simpler. The "left" side primary key value is already in memory so there's no need to reload and redisplay the list box while it's being displayed.

On exiting the procedure (whether a browse or a form) any remaining changes are saved:

Code Block
#At(%WindowManagerMethodCodeSection,'Kill','(),BYTE'),PRIORITY(1100),DESCRIPTION('Initialize M2MCheckboxList')
%M2MClassInstanceName.SaveEnrollmentData()
#EndAt

This raises an issue for further development: depending on how the class and template are used it may be better to ask whether the changes should be saved, or in the case of a form the saving of changes should probably be aligned with the form action. 

The next block of template code (potentially) refers to both the left and right browses:

Code Block
#AT(%BrowserMethodCodeSection,,'TakeNewSelection','()'),PRIORITY(9000)
    #If(%LeftFileIsInBrowse)
        #If(%ControlInstance = %LeftBrowseControlInstance)
%M2MClassInstanceName.LoadEnrollmentData()  
        #EndIf
    #EndIf
    #If(%ControlInstance = %RightBrowseControlInstance)
%M2MClassInstanceName.DisplayCheckboxData()
    #EndIf
#EndAt

This is one of two parts of the template that caused me a lot of grief. I have a template that is attached to one browse control via the REQ attribute, and it needs to generate code into another browse control. I had actually been down a similar road years before when I wrote a template chain to generate ASP.NET MVC code from Clarion, but it took a while and a few conversations with Mike Hanson before I found the answer. 

#AT statements have the ability to progressively filter the possible locations where they can generate code. For instance, the very next #AT statement in the template looks like this:

Code Block
#AT(%BrowserMethodCodeSection,%ActiveTemplateParentInstance,'SetQueueRecord','()'),PRIORITY(9500)
#! #AT(%BrowserMethodCodeSection,,'SetQueueRecord','()'),PRIORITY(9500)
%M2MClassInstanceName.SetCheckboxIcon()
#EndAt

Because my extension template instance is a child of the browse template instance, by specifying %ActiveTemplateParentInstance as the second parameter I can restrict generation to just that browse, and by further specifying the procedure name and the prototype list (empty in this case) I can say I want to generate the code into a specific method. The number of parameters on the #AT depends on the number of parameters in the corresponding #EMBED statement, which in this case is:

Code Block
#EMBED(%BrowserMethodCodeSection,'Browser Method Code Section'),%ActiveTemplateInstance,%pClassMethod,%plassMethodPrototype,PREPARE(,%FixClassName(%FixBaseClassToUse('Default'))),TREE(%TreeText & %CodeText)

The help points out that the instance values are omittable:

Image Added