![]() |
|
Published 1999-06-01 Printer-friendly version
With Clarions ABC templates, there are many assumptions made about the way you work. Normally these are just fine, but occasionally they can be annoying. One of my pet peeves is the way that the AddSortOrder method works.
This class method belongs to the ViewManager. When you access records using this class, you must define at least one "SortOrder". (In the case of a browse, you may have more than one.) Each of these SortOrders represents an "Order" and "Filter" pair, and you may also have a "Range" involved.
By the way, dont get "SortOrder" confused with "Order". "Order" is merely the sequence in which the records are presented to you, while "SortOrder" represents not only the Order, but also the Filter and Range. The way I remember that is that "Sort" is more an operation (and more significant), while "Order" is merely an attribute. Another way to remember is that VIEWs can have a PROP:Order, but not a PROP:SortOrder.
These three elements (Order, Filter and Range) interact in a rather convoluted manner. The simplest example is:
MyView.AddSortOrder
This adds a new SortOrder, but at this point no Order, Filter or Range applies. You will get all records in the file, and you cannot predict the sequence. We can manually add an Order (sequence) with the following method:
MyView.SetOrder('Inv:Date')
We can also add a filter in a similar fashion:
MyView.SetFilter('NOT Inv:Paid')
You can actually have multiple filters, each having its own ID. To do this, just add a string as the second parameter to the SetFilter call. When the various filters are applied to the VIEW, they are combined in alphabetical order by ID.
Adding a Range is somewhat more complicated. Internally, a Range is simply an additional filter, which is applied before any of the regular filters. It must appear first in the filter expression, because its important to restrict the records first by range, then by any additional filter criteria. The VIEW engine tries to match keys against filter components in the order in which they appear in the filter expression.
The problem, though, is that the ABC classes wont allow us to add a range without first specifying a key. This is done for our own protection, so that we dont apply a range for a non-filtered key, and end up with an unacceptably slow browse. This key is specified back in the original call to AddSortOrder:
MyView.AddSortOrder(Inv:CusKey) MyView.AddRange(Inv:CusNo, Cus:No)
This call to AddRange limits the processed invoices to those with a customer number matching the current customer in memory. So far so good. After testing this browse, youre pretty happy with the results. However, your Inv:CusKey happens to contain the Inv:CusNo then the Inv:Date. You had defined the key this way because you excepted to have many invoices for each customer, and you wanted them displayed in date order in most situations.
In this browse, though, youve decided to view the unpaid invoices on top, followed by the paid ones. Additionally, you want to see the newest invoices within each unpaid/paid group at the top and the older ones at the bottom. "No problem," you say to yourself. You spotted a template setting called "Additional Sort Fields". Youll just enter "Inv:Paid,-Inv:Date" there. Unexpectedly, the browse seems to look exactly the same as it did before. You scratch your head and wonder what went wrong.
Although the answer is not obvious, its certainly explicable if you start nosing around the internals of the ViewManager source code in ABFILE.CLW. The problem is that by specifying the key in the call to AddSortOrder, the class assumed that you wanted all of the fields in the key as your main sort order. This includes the Inv:Date component. So after the initial call to AddSortOrder, your order is:
Inv:CusNo, Inv:Date
Your additional sort fields are specified with the following call:
MyView.AppendOrder('Inv:Paid,-Inv:Date')
So after this call the combined order expression is:
Inv:CusNo, Inv:Date, Inv:Paid, -Inv:Date
Thats why your browse appears the same in both instances. The invoice records are sorted by customer number, then by invoice date. Within a particular invoice date, it will sort paid and unpaid, but normally a customer doesnt have multiple orders on the same date. Hence, you need to find another solution.
Your next attempt might be not to specify a key, and to declare all of the sort fields in the "Additional Sort Fields" setting. You'll remember, though, that without a key you cannot specify a range. "Fine," you say to yourself. Youll specify the range settings in the Filter statement. Suddenly you discover that your scrollbars dont work, and neither do your locators. Not only that, but youre stuck manually typing filter and order statements, which is counter to the underlying philosophy of Clarion's templates.
Wouldnt it be nice if the template were to ask if we wanted to use the entire key for sorting purposes, or just some of it, or none of it? (i.e., what is the last field component to use in the basic order.) This could be passed through as a second, optional parameter to AddSortOrder. With a default of "All", existing apps would be unaffected. Something like this will probably come in a future version of Clarion, but for now were stuck with the status quo. That means we still need a solution.
So we jump back into the ABC source to discover more of its secrets. Eureka!!! It turns out that we can use the "SetOrder" method to override the existing order. Since the key and its associated order are required only to help specify the Range for the ViewManager, there should be no harm in overriding it after its gotten the message. As we dig a little further, we confirm that this is true. It remembers the key and range settings, irrespective of the current Order setting.
So now all we have to do is add a manual SortOrder command in the ThisWindow.Init method whenever we want to override the browse order. While this is certainly a viable solution, its a little awkward. First you must find the appropriate point in the source code. If you are using conditional sort orders, then you must determine which sort order corresponds to your desired override situation, then use the SetSort method synchronize it. Finally, after calling SetSort, you use SetOrder to override the sequencing. Whew! Thats a lot of work, and its really fiddly.
So you ask yourself, "If it was a template that got me into this mess in the first place, couldnt another template get me out?" The answer, in a word, is "Yes!" This extension template will attach itself to the existing Browse, and it will automatically perform the required SetSort and SetOrder for any conditional sort orders that have "Additional Sort Fields". This will override the implicit order assumed from the specified key.
For our template to connect itself to the existing browse, it must use the REQ attribute:
#TEMPLATE(mhClarionOnline,'Mike Hanson''s Clarion Online Articles'),FAMILY('ABC')
#EXTENSION(BrowseOrderOverride,'Override Browse Order using "Additional Sort Fields"') |
,DESCRIPTION('Override Browse Order using "Additional Sort Fields"'),REQ(BrowseBox(ABC))
Now we must determine the applicability. To do this we have to check the settings for the browse. The Browse template actually makes things easy. Normally we would have to check the default settings separately from the settings for each conditional sort order. However, the Browse template loads all of these settings into a more organized structure that we can access directly.
The lead symbol is %BrowseAccessID. This multi-valued symbol contains one entry for each conditional sort order, plus the last will also be the default settings. If there are no conditional settings, then there will be only one entry here: the default.
Following that are two other symbols of which we must be aware: %BrowseKey (the key setting) and %BrowseOrderStatement (the Additional Sort Fields). Now that we have this information, we can generate the necessary code into the ThisWindow.Init method:
#AT(%WindowManagerMethodCodeSection,'Init','(),BYTE'),PRIORITY(8150)
#FOR(%BrowseAccessID),WHERE(%BrowseKey AND %BrowseOrderStatement)
#IF(ITEMS(%BrowseAccessID) > 1)
%ManagerName.SetSort(%BrowseAccessID)
#ENDIF
%ManagerName.SetOrder('%'BrowseOrderStatement')
#ENDFOR
#ENDAT
You'll notice that I also added a condition around the call to SetSort. If there is only one sort order for the browse (i.e. no conditional orders), then the current SortOrder is the only SortOrder.
Thats all there is to it. I should mention one caveat, however. This template will prevent the use of locators for those overridden sorts. Next month, though, I'll present a new type of locator to alleviate this dilemma.
This little example shows that not all templates need to be barn raisers. Some will simply make your life a teeny bit easier. Although its certainly beneficial to acquire templates from 3rd parties, they cant always predict your every need. When its something small like this, its often easiest to roll your own.
For those of you who would have hand-coded the solution rather than doing it with a template, let me stress that the template solution is malleable: If the method for the solution changes in the future, you can change the template and all your prior cases are automatically upgraded. Thats a whole heck of lot easier that it would be if you had manually done this it in a hundred places. (Just finding them all would be the hard part.)
So take the plunge and start writing your own templates. Not only will it increase your productivity, it will also make your programs easier to maintain. And to top it off, its tons of fun!!!
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $184
(includes all back issues since '99)
Renewals from $134
Two years: $274
Renewals from $224