How ABC Handles Multiple Sort Orders (Part III)

By Steven Parker

Posted June 21 1999

Printer-friendly version

Read Part I

Read Part II

When I first undertook the project to allow users to store additional sort orders in a file and automatically provide those orders on browses and reports, there were several things I knew.

I knew that additional orders could be created and I also knew that additional tab controls could be created. I had only a vague idea of how to do either of these tasks, never having had to before, but I did know these things could be done.

Having looked at the code generated by the templates for different orders on different tabs and the Solodex example for runtime tab creation, I knew that implementing each feature reduced to only a few lines of code each, very few as it turns out. I also "knew" that understanding what was happening, especially in adding sort orders, would mean tracing method calls and that would surely not be a matter of just "a few lines of code."

While I will certainly settle for code that works, I've always preferred understanding how and why it works. Understanding allows me to not only extend solutions into strategies but helps avoid problems in the first place.

I was right on both counts.

From the beginning, it seemed to me that the really hard part was going to be associating the additional sort orders with the additional (runtime) tabs. That is, it would be hard to make sure that the sort order records in the file, the selected tab and the applied sort actual mapped one-to-one. Not simply one-to-one but predictably and without variation. Everything has to come together at runtime in just the right way.

I do most of my programming with a pencil and paper. And as I write this I am looking at my original notes. They are exclusively concerned with setting up the tabs so that each links to a single, predictable sort order. My notes indicate that I finally decided to loop through the file containing the sort orders and read the needed fields to a queue. At the same time, I determined how many additional tabs had been created, the ordinal position of each new tab relative to the current record and adding a number indicating that ordinal position to the queue (mine, not the browse object's). When the user changed tabs, I was planning on using the tab position (Choice(?CurrentTab)) to retrieve the matching queue record and ... you get the idea.

A typical programmer's solution.

I could not have been more wrong. Ensuring that the selected, additional tab and the additional sort order match could not have been easier. (Ok, it could have been easier but no one has a template available that does what I need.)

Basic Considerations

In the last installment I demonstrated that user defined sort orders need to be added to the Sort property after the sort orders supplied by the templates. Where n is the number of tabs created within the IDE, adding custom orders after the template's ensures that the first n queue entries and the first n tabs match in the standard way, as shown in Listing 1.

Listing 1. Sorting by tabs.
IF CHOICE(?CurrentTab) = 2
  RETURN SELF.SetSort(1,Force)
ELSIF CHOICE(?CurrentTab) = 3
  RETURN SELF.SetSort(2,Force)
ELSIF CHOICE(?CurrentTab) = 4
  RETURN SELF.SetSort(3,Force)
ELSE
  RETURN SELF.SetSort(4,Force)
END

The key to this kingdom is that, as the developer of the application, I know the value of n. I know how many tabs were created at design time and, therefore, I know the initial number of entries in the sort queue. More important, I know the ordinal position of each.

That also means that I know that any supplemental orders and any runtime tabs start at n + 1. To ensure that queue entries and tabs are mapped in the same order, to ensure the mandatory correspondence, they only need to be created in the same order, using the same Key.

Too simple. (Proper prior planning ....)

That leaves only:

Activating Sorts on Tab Change

For all of this work to pay off, the additional tabs must actually activate the corresponding additional order.

The code in Listing 1 above is the standard template generated tab-changing code for a four-tabbed browse. It contains all the clues needed to finish this project.

The logical core of this code is the use of CHOICE(?CurrentTab) to calculate the first parameter of the SetSort() method. SetSort() then does the actual work of re-setting the sort order.

For the sake of example, suppose you have a sheet with two tabs. This means that the first custom order created at runtime will be queue entry number three and the first runtime-created tab will be CHOICE(?CurrentTab) = 3.

This means that for all dynamically created tabs:

Listing 2.
RETURN SELF.SetSort( Choice(?CurrentTab), Force )

ought to retrieve the correct sort queue record. In turn, this means that the desired order will be correctly set. I'm just "requisitioning" the standard code, after all.

Of course, it cannot possibly be quite so simple. It isn't.

The statement in Listing 2 will give incorrect sorts for the default tabs, tabs created in the IDE. Remember that in the case of two tabs, the queue entry for tab two is added first then the default order (tab one) is added to the queue. So, what you really need to do is use this code for runtime tabs only, leaving the generated code to handle "static" tabs, something more like:

Listing 2.
IF CHOICE(?CurrentTab) > n
  RETURN SELF.SetSort( Choice(?CurrentTab) , Force )
End

The question now is:

Where is this code placed?

And the answer is: "It depends."

It does depend. If the underlying browse has only one key and, therefore, only one tab, no tab switching code will be generated by the templates. There will be no check on Choice(?CurrentTab) because ?CurrentTab only has one position.

If there is more than one tab, code like that above will be generated. In this case, a structure like that in Listing 1 will be generated by the templates.

One Tab: In this case, since the templates supply nothing, you need to supply everything. The code in Listing 2 does everything necessary and it seems pretty clear that it should go in the ?CurrentTab ... NewSelection embed.

Unfortunately, the code in Listing 2 will not work. It will fail to compile.

It will not compile because the browse object is not in scope here (the NewSelection embed is not a derived virtual method of the browse object, it is an event embed). But SetSort is a property of the browse object and the procedure knows about that, so:

RETURN BRW1.SetSort(Choice(?CurrentTab), 1 )

will do the job.

In the sample app that accompanies this article, check the WithNewTabs_Corrected procedure ("corrected" for supplementary orders being incorrectly activated when the browse is first opened; this was discussed in the previous installment). This placement of the revised code does just what we want.

Multiple Tabs: When there is more than one tab in the sheet before the ad hoc tabs, you want to use the code in Listing 3 as is.

Where?-Check the template code (Listing 1) and you will see that there is an ELSE clause.

Runtime tabs will always return a value greater than n and will, therefore, trigger the ELSE clause.

This means that code handling the created tabs must come before the template generated code. As this code tests only for values greater than the number of tabs created in the window formatter, the standard generated code will still do its job correctly.

Opening the Embeditor and finding the standard tab change code shows that the embed immediately preceding is Before Refresh Window for Browse Box (see Figure 1).

Figure 1. Embedding the tab change code.

sort3_1.gif (6196 bytes)

If you check the Embed tree, this is a Legacy embed. I can't say that I much care but if you're an OOP purist, Local Objects ... BRW1 ... ResetSort is the corresponding ABC virtual method.

So, there you have it, easy as 1-2-3:

Figure 2. The embed tree showing the embed point.

sort3_2.gif (8409 bytes)

  1. create your tabs After the window is opened
  2. add your new sort orders after any existing key are added
  3. add the code to act on a tab change.

One caveat: unless you limit the number of records in the sort file, you have neither any idea of how many tabs will be created nor any control. Created tabs could well end up stacked on top of each other in a most unattractive fashion. Play it safe and select a scrolling option for the tabs (on the second tab on the Sheet's Property Worksheet) whether you think you need it or not.

An interesting consequence of this solution is that it will continue to work even if Topspeed changes how it adds sort orders to the queue. If the templates are changed so that tab one is added to the queue first or so that there is no ELSE clause, this solution will still do what is expected of it. The only thing that Topspeed must do is maintain the AddSortOrder and AppendOrder methods.

Reports

I mentioned reports as requiring runtime sorts.

This particular requirement would not seem to be solved by the technique developed here. Indeed, it is not. However, the People example provides a variation applicable to reports. See the PrintPEO:KeyID report, ThisWindow (ReportManager) ... OpenReport (before Parent Call) embed.

Summary

Once you understand how the ABC templates implement multiple sort orders, duplicating and adapting that behavior to whole new program functionalities is not at all hard. The total code required is 22 lines or less.

What is difficult is tracing and understanding the ABC method calls. However, it is not necessary to master all the complexities of the browse object, just enough to comprehend the logic.

Download the source code

Steve Parker started his professional life as a Philosopher but now tries to imitate a Clarion developer. He has been attempting to subdue Clarion since version 2007 (DOS, that is). He reports that, so far, Clarion is winning. Steve has been writing about Clarion since 1993.

Article comments

Post a comment

You must be logged on to post comments.

Clarion Roadmap

Try the roadmap (beta)

Search ClarionMag

 

Advanced search

From the archives

Unit Testing Webinar Workshop Takes On Dates/Times

3/31/2011 12:00:00 AM

Recently John Hickey and David Harms hosted a webinar workshop on unit testing, using Pierre du Toit's article on Clarion and Excel dates and times as a source for a utility class. John and Dave learned a few things about the process, and hopefully the participants did too.