![]() |
|
Published 1999-12-07 Printer-friendly version
In part 1 of this article I discussed the ViewManager's initialization and cleanup methods. What's left is the code.
These three methods really embody the vast bulk of "the smarts" within the ViewManager. Their job is to construct a filter and order clause to provide a record set equal to the one currently requested. They also have an alternative agenda, which is to avoid resetting the VIEW's order and filter clause unless absolutely required. This avoids busy work in the view driver.
This function really does three separate jobs. Firstly it constructs a filter equivalent of any range limits provided; then it concatenates any filters provided; finally, it applies the filter to the view and monitors the error condition.
The CASE statement is simply to branch between current, single and file range limits, all of which result in a filter of the form "field1 = xxx AND field2 = yyy" and the pair range limit which produces "field1 >= xxx and field1 <= yyy".
For the common case the code sits in a loop for each element of the range limit queue. For each element the field name is extracted from the file manager, then CasedValue is called to compute the right hand side of the equals sign for the given condition. CasedValue allows for the right hand side being a string (in which case quotes are used), the key being case insensitive (in which case an UPPER will be present of the field name and should be present on the constant name) and even the value containing quotes!
The range filter assignment line has an interesting tweak, which is the use of an inline choose statement:
CHOOSE(I = 1,'',' AND ')
This solves the age old problem of having a list of items that you want separated (with an AND in this case). The traditional solution (using an IF statement before the concatenation) can make a simple loop look complex. The CHOOSE handles things in a very compact way.
The Pair code is a little more complex and illustrates nicely the treatment of key components which come before the component being range limited. If you look at the case within the loop the second branch is only taken for those major components. They are simply "=" limited, exactly the same as for a current value range limit. In other words, the standard ABC library deals with multi-component range limits without any clever tricks being needed.
The RRL-1th element is the lower bound of the range leaving the upper bound to be computed outside the loop.
The filters supplied by SetFilter are then appended in turn (there can be any number of them, each with a different ID). Each supplied filter is placed inside parenthesis to avoid any unexpected operation precedence problems.
Finally the filter is assigned and the error trapped.
This method doesn't have any complexity. It just assigns the order clause and traps any errors.
ApplyRange is where the system gets some attitude. The idea here is that sometimes the window manager knows "things have changed" and wants to alert the ViewManager that it needs to refresh itself. But the ViewManager shouldn't refresh itself if nothing of interest has actually changed. So ABC has the ApplyRange method.
For every range limit (other than current value) a mirror value is stored to reflect the state of the range limit the last time the browse was refreshed. When ApplyRange is called it checks the new values against those in the mirrors. If nothing has changed then ApplyRange simply returns; otherwise ApplyFilter is called to handle the changes in the data.
For the Pair case both the upper and lower bound have to be checked. This is done by comparing the right (which acts as a buffer) with the left. If there is a difference then left is assigned to right (for both bounds) and ApplyFilter is called.
There is a neat trick here: the Single clause is introduced by OROF not OF. This means that the Pair case will also fall down into this code.
The File case looks a bit more complex but it isn't. For the file range limit both left and right values are used (the left is the file being limited, the right is the file doing the limiting) so if the file doing the limiting has changed the new value is assigned to the buffer part of the BufferedPairsClass before the ApplyFilter is done.
The following methods are really the ones that are called to do things to the view during normal operation of the ViewManager. They have names and semantics similar to equivalents in the underlying file managers and relation managers.
The purpose of this method is to allow a record to be cleared/prepared for sending to an Update (or similar code) for insertion. This work is done in three stages,
These methods close and open the view respectively. They are extremely simple. The Opened flag allow them to be called on a "check if it is open" and "check if it is shut" basis without generating errors. The Open method also applies the presently active filters and order so that the static filter/order of the view (usually declared in the generated CLW file) doesn't have a chance to load any records before they dynamic filter/order (which override the static one) is applied.
This method switches the presently active sort order. After this call further calls to (for example) SetFilter/Reset mean "perform this action on the present sort order." This is one of the main benefits of this manager, as I detailed earlier.
The value returned means "did anything happen." This is part of the attitude mechanism: if a zero is returned the caller knows it doesn't have to progress with any code that would been required for a new sort order. For example, the browse watches this to see if a reload might be required.
Reset on a ViewManager (and on any ABC object for that matter) means "bring this object up to speed with any external data it references." In this context it means bring the view up to date with any data on the disk and position it relative to data in the underlying file record buffers. The Locate byte denotes how many leading components of the presently active order are taken into account when performing the locate. In other words, if Locate is zero (or you call the unparameterised form of Reset) no location is done and the view is positioned at the beginning of the record set. If locate is (say) 2 then the first two fields from the order clause are used to perform the position.
Next and Previous read the next and previous records from the current data set. This is pretty much just a call to the underlying view. Additionally the error conditions are converted into ABC error levels.
If the record is valid according to the view criteria then the ValidateRecord method is called. This can specify one of Ok, Filtered and OutOfRange. The OK from ValidateRecord simply causes a return from the Next/Previous method with a Level:Benign error rating. An OutOfRange return is converted into a Level:Notify, which is interpreted by the rest of ABC as an "End Of File." In other words it stops all view processing until the next reset. The Filtered case is handled by simple continuation. This causes the outer loop to cycle causing the next record to be read from the view. In this way no "invalid" records will ever be returned from the Next/Previous methods.
This method doesn't really belong in this section but it is so closely related to Next/Previous that I decided to cheat. The default implementation of ValidateRecord is useless - it just returns Record:Ok. It is here so that people can override it (by dropping embed code into the ValidateRecord embed point) and thus define an additional programmatic filter for what does and doesn't constitute a valid record. ValideRecord should return Record:Ok, Record:Filtered or Record:OutOfRange as defined above.
The remaining methods are really housekeeping and are designed to allow people to fiddle with the internal state of the ViewManager object without having to know its structure. As such they are generally simple to use and implement. They all require the previous use of SetSort which sets the sort order to be acted upon.
This returns the position within the present sort order of the field which is the free element. You can think of this as the "number you need to pass to Reset to get a search using fields up to (and including) the free element." For example, if you have a three component key, and have range-limited the first element, this function will return 2. Now if you have values in component one (the fixed element) and component two (the floating one) a Reset(2) will locate to the first record matching both of those elements (but ignoring the third).
This method returns the bind name of the free element, which allows derived classes to perform some kind of location/free element manipulation other than a simple reset. For example the filtered locator class uses this function to construct a filter of the form SUB(GetFreeElementName(),1,2) = 'FR' or similar resulting in SUB(CUS:FirstName,1,2) = 'FR'.
SetOrder is directly equivalent to setting the order clause of the present sort order for the underlying ViewManager.
These two methods manage the filtering system. What we were aiming for was to enable people to write additions to (for example) a browse that enabled additional filterings. The classic is a QBE filter or a multi-component locator. The traditional problem has been how to set a new filter without clobbering the one provided by somebody else. Using the one parameter SetFilter you can't; whatever you set overrides the filter provided by the filter prompt in the template. However the two parameter filter allows you to specify a unique identifier for the filter being passed in. Provided that identifier does not clash with anyone else's (be inventive, this is a string) then all the set filters will be concatenated before being sent to the view.
There is an implicit priority mechanism in that the strings are alpha-sorted before going to the view driver. This may be important efficiency-wise as many expression evaluators (including ours) perform left-to-right short-circuit evaluation of boolean conditions. (Put another way, if your doing something slow, put it last filter as the system probably will have thrown a record away before the code gets called).
Phew! We have finally gotten to the end of ABFILE.CLW. Kinda tiring, huh? Well yes, although it should be remembered that ABFILE is by far the largest module in the ABC system (45% bigger than ABBROWSE) and the relation manager is probably the most complex part of the system.
I trust that the tour has provided you some insight into the operation of "the spine" and also into the coding techniques, styles and methodologies employed.
That said, the number one lesson I hope you have gleaned is that all the code is there, it is all fairly approachable and once you understand it it really does begin to make sense.
Honest.
David Bayliss is a Systems Architect for The TopSpeed Development Center. He has worked upon TopSpeed's compiler and was the chief architect of the Application Builder Classes.
Copyright © 1999-2009 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: $169
(includes all back issues since '99)
Renewals from $119
Two years: $269
Renewals from $219