The Power of Views - The New View Manager

by Mike Hanson

Published 1997-10-01    Printer-friendly version

In this article, I'm going to cover some of the theory, background and tricks concerning Clarion's VIEW technology. This is especially important, with Clarion 4's increasing dependence on this powerful tool. Before we get too deep, though, let's take a look at the big picture. As programmers we have to deal with many details. Sometimes this is necessary, but our goal should be to let the computer worry about the details, and let us simply "direct the action".

There are always situations that require hand-coded file access. This involves our remembering the files, keys, fields, relationships, etc. If we forget any of these elements, we may be lucky enough to get corrected quickly by a compiler error. Or worse, the program doesn't work properly, but we don't notice until later. Even more terrible, it may work fine for a while until the database structure is changed to accommodate a new requirement. Suddenly our code becomes invalid and the whole house of cards comes crashing down.

It's these things that we, as programmers, need to minimize. Well, VIEWs allow us to do just that. It is an abstracted file access facility that worries about the details, and changes its strategy when the underlying rules change. We can always depend on it to do the best job that it can. That doesn't mean, however, that we should be ignorant of the strength and shortfalls of VIEWs. By understanding what they do and how they work, we will be better equipped to optimize our own implementations.

What is a VIEW?

If you remember back a couple of months to my OOP article, I stated that something was "abstracted" when the details and complexity were hidden and it could be perceived as a high-level entity. Although the VIEW itself is not an OOP construct, this particular concept still applies. We don't care about keys, key components, relationships, linking fields, etc. All we want is a set of entries from one or more related files, possibly served up in a specific order. Even if you are accessing only a single file, it is helpful to use a VIEW. When working with multiple related files, it is a godsend.

When you declare a VIEW, all that is required is the primary file. You can optionally request fields from that file using the PROJECT attribute. Additional related files can be added using an embedded JOIN structure. Each of those related files can have its own projected fields.

Regarding projected fields: Even though the VIEW accesses all specified files under the covers, it is only updating the PROJECTed field buffers. If you care about a particular field, make sure that you project it in the VIEW. Do this for all files, even the primary file! (Trust me on this.) If you are using a Process or Report procedure to create the View, you can PROJECT fields by adding them as "Hot Fields".

VIEWs can also have a FILTER attribute. This controls which records are retrieved by the VIEW. Whenever possible, the VIEW engine will use keys in the primary file to speed the search for matching data. This includes boolean expressions like Name='X', Salary<25000, etc. Of course keys will not help if you are searching for a substring. A filter expression can be any valid Clarion expression that would work in an IF statement. Don't forget to BIND all elements of your filter.

The other helpful feature is the ORDER attribute. This specifies the sequence of records processed by the VIEW. Again, it will use keys if possible. However, if a FILTER can use a key to narrow down the solution set, the VIEW will normally use this first. It will then sort the subset of records in memory before giving you the first record. Each element of the ORDER statement must be separated by a comma, and preceded by a plus (+) or minus (-). You can also use expressions, but don't forget to BIND all of the elements.

Here are some sample VIEW structures:

ViewCus      VIEW(Customer).
ViewCusInv   VIEW(Customer),FILTER('Inv:Date'),ORDER('Cus:Name')
               PROJECT(Cus:Name)
               JOIN(Inv:CusKey, Cus:No)
                 PROJECT(Inv:Date)
               END
             END
ViewInvCus   VIEW(Invoice),FILTER('Inv:Date'),ORDER('Cus:Name')
               PROJECT(Inv:Date)
               JOIN(Cus:NoKey, Inv:CusNo)
                 PROJECT(Cus:Name)
               END
             END

Notice that the first VIEW is useless, as it doesn't specify which fields to retrieve with PROJECT, it doesn't narrow the record selection with a FILTER, and it doesn't control the processing sequence with ORDER. It is still, however, a valid VIEW structure (i.e.: it will compile without errors). It will even work (for non-SQL/ODBC files).

Although it may not be immediately obvious, the next two structures are functionally identical. That is, they will give you the same output. The first will be faster, assuming there is a key on customer name. The second view structure will pull the records into memory (at least the customer name and invoice date) and sort them in name order. This is one example where optimizing your VIEW structure can make a big difference.

This brings us to the concept of turning VIEWs "inside-out". In our above example, we wanted to print our records in customer name order. Since we didn't filter our records, the customer file is the obvious choice for primary file. If, however, we had decided to print a subset of records based upon invoice date, and there was a date key in the invoice file, then we may have wanted to use the invoice file as the primary file for the VIEW. This can have a huge impact on the speed of your Reports and Processes.

Almost any VIEW structure can be inverted in this fashion. Be careful, though, of the "forced primary record" rule. Clarion's VIEW structures will always give you at least one record for each record in the primary file, regardless of whether there are related records. This is why I've included the FILTER('Inv:Date') above. It tells the VIEW to process only those Customer records where there is at least one Invoice record.

This works because of a handy aspect of the VIEW engine. If there are no related records for a particular primary record, it will clear the related file's fields. By specifying that we want only entries with a non-zero Invoice date, we eliminate customers without invoices. Of course, if your invoice date could be zero, then you may want to use the invoice number instead. Use whatever works in your situation.

When there are more than two files involved, the VIEW structures can start to look different than each other. Don't worry, they will still work properly. Here is another example:

View        VIEW(Customer)
              JOIN(Inv:CusKey, Cus:No)
                PROJECT(Inv:No)
                PROJECT(Inv:Date)
                JOIN(Itm:InvKey, Inv:No)
                  PROJECT(Itm:PrdNo)
                END
              END
            END
View        VIEW(Invoice)
              JOIN(Cus:NoKey, Inv:CusNo)
                PROJECT(Cus:Name)
              END
              JOIN(Itm:InvKey, Inv:No)
                PROJECT(Itm:PrdNo)
              END
            END

There's something interesting that you should note here. In the above VIEWs, we are dealing with Customers, Invoice Headers and Invoice Line Items. We intuitively understand that there are many Invoices for each Customer, and many Items on each Invoice. It would be very natural for you to assume that the VIEW needs to know this too, but you would be wrong! The VIEW engine doesn't care. It just finds all related records where the linking fields are the same. If there is only one record, fine. If there are thousands, that's still fine. If there are none, then that's fine too.

The only thing that you must realize is that it cannot do multi-branch retrieval. For example, you cannot process all the Calls for a Customer and all the Sales for a Customer using the same VIEW. Instead, you must create two VIEWs to handle the situation, and combine the results manually.

Hand-Coded Access versus VIEWs

Let's look at a coding example. We want to find all customers without any sales. Our hand-code might look something like this:

SET(Customer)
LOOP
  NEXT(Customer)
  IF ERRORCODE() THEN BREAK.
  CLEAR(Inv:Record,-1)                  !**
  Inv:CusNo = Cus:No                    !**
  SET(Inv:CusKey, Inv:CusKey)           !**
  NEXT(Invoice)
  IF ERRORCODE() OR Inv:CusNo <> Cus:No !**
    Count# += 1
  END
END

The four lines marked with !** require an intimate understanding of the file structure. Also, this code could easily be broken by changing the key definition. Let's look at the same example using a VIEW:

View            VIEW(Customer), FILTER(Inv:Date=0)
                  JOIN(Inv:CusKey, Cus:No)
                    PROJECT(Inv:Date)
                  END
                END

  CODE
  OPEN(View)
  LOOP
  NEXT(View); IF ERRORCODE() THEN BREAK.
  Count# += 1
  END
  CLOSE(View)

This code is much cleaner. Even the VIEW structure contains fewer details about the files. (The engine still has an intimate knowledge of what's going on.) In most cases you can arrange for the VIEW structure to be written by a template (more about this later).

Let's just recap quickly: a VIEW enables us to process one or more related files, restricting the records with a FILTER and sorting them in ORDER. The VIEW engine optimizes the search to the best of its ability, and we can optimize this via our choice of the primary file.

The ViewManager Class
(All you wanted and a little bit more)

The ViewManager is a CLASS in Clarion 4's new Application Builder Classes (ABC). It wraps itself around a VIEW, allowing much more flexibility. It is used by the Process and Browse procedure templates to perform all file access. (All of that code that was written into every Process and Browse procedure is now hidden within the ViewManager.) Here's where the class sits with the other ABC classes:

Figure 1: The View Manager class
The View Manager class

Let's look at each of these very briefly to understand their basic purpose.

  • The FieldPairs class is used when you want to manage sets of one or more "linked" fields. For example, a Browse accesses fields from a file. Some of these fields will be displayed in the list box, and must be in the QUEUE structure. Each correlated file field and queue field is a "field pair".
  • BufferedPairs are simply FieldPairs with a third field used as a temporary storage buffer. It is needed when one of the other two fields might change during operations, and you need to hold onto a copy for further reference.
  • The FileManager performs direct file operations. It understands most of the intimate details and operations associated with each file. It also handles things like field priming for new records (although some of the priming is also handled by the ViewManager class).
  • The RelationManager understands how files are related. It handles the referential integrity code when performing updates. When you are using ABC, you are urged strongly not to use Clarion's regular ADD, PUT and DELETE commands. Instead, use the file's equivalent class methods from its RelationManager (e.g.: Access:Filename.Update).
  • The ViewManager manages VIEWs. It also handles some field priming (e.g.: auto-numbering the detail records for a related invoice header record).
  • ProcessClass and BrowseClass are used by the Browse and Process procedure templates. The classes replace the extensive code that was formerly generated into each and every procedure.

The ViewManager handles multiple sorts (think of the tabs on a Browse window). Each of these sorts has an ORDER clause, and optional FILTER clauses. That right - you have more than one filter! These are combined based upon their specified priority. Higher priority filters are likely to help with key selection (like range selectors).

The ViewManager class has a virtual function called ValidateRecord. This allows you to have your own hand-coded record restrictions. After a record has made it through the regular FILTER expression, your virtual function gets to perform a final check on it.

To use the ViewManager, you must define your VIEW, instantiate the class, initialize the object, and add one or more sort orders. Each of these sorts can have one or more order field segments, and zero or more prioritized filter segments. After all of this is done, you can get down to the work of actually using the VIEW to process records.

Now you're likely thinking that using the ViewManager class is too hard. Well, if you're hand-coding, then you're right! Here's where templates can save the day. The templates can ask us a few simple questions, then handle the complex interface with the class. Clarion has the best of both worlds: the benefits of OOP coupled with the simplicity of templates.

The mhVIEW Template

This is an extension template that I wrote to simplify my VIEW usage. I describe the desired VIEW in much the same way as you would for a Process. I had a couple of extra requirements like additional field pairs and an optional ORDER clause. Finally, I was intending to access the VIEW from my own hand-code.

This extension may be added to any procedure, including a Browse, Process, Report, Window or Source. (If you are using it in a Source procedure, don't forget to include the mhSourceFiles extension too.)

The file schematic is specified in the same way as all other templates in the Files window. I can change the default name from "View", in case there is more than one instance used in the same procedure. I can specify a filter, Range limits, and additional field pairs with multiple comparison operators (e.g.: Pre:Field=Loc:Field, Pre:Field<Loc:Field, etc.). My goal for coding was to use the following algorithm:

  • Open the VIEW
  • Process the entries
  • Close the VIEW

For example:

DO OpenView
LOOP
DO NextView
IF ERRORCODE() THEN BREAK.
!Do something
END
DO CloseView

If I wanted to check whether a record existed, the code might look like this:

DO OpenView
DO NextView
Exists# = CHOOSE(NOT ERRORCODE(), True, False)
DO CloseView

As you can see, this code is very simple, and very abstract. It doesn't contain any details of the file structures, and it doesn't need them. The VIEW will handle it for me. Its structure is written by the template, which is driven by the dictionary. If the dictionary changes, so does the VIEW. This makes for very dependable code.

If you are interested in this template, it's free! Go www.BoxsoftDevelopment.com, and look for MHTPL???.ZIP in the Download section. As of October 1, 1997, it doesn't support the new ABC templates. (I just got C4 Beta 2, and I haven't been able to get the template moved over yet.) Look for new versions of the template in the coming weeks and months.

Conclusion

I hope this has given you a better understanding and comfort level with Clarion's VIEW technology. It is a powerful tool that enables you perform minor miracles, while not worrying about the details of keys, components, relationships, etc. It also means that your code will age well, which will give you fewer headaches down the road.

It seems that each Clarion Developers Conference generates a "pet phrase" that is continually bandied about the sessions. In Arizona 1996, it was "Does it do page numbers?" In Florida 1997, it was "You don't know, and you don't care!" The bottom line is that you can depend on VIEWs to get the job done. If you don't optimize them correctly they'll be slow, but they'll still work. The same claim cannot be made for hand-code.

Printer-friendly version

 
 

Search

 

Advanced Search
Topical Index

Related Articles

Subscribe to
ClarionMag

One year: $189

(includes all back issues since '99)

Renewals from $139

Two years: $289

Renewals from $239

More Info

Subscribe Now!

ClarionMag Blog

RSS Feeds

Updates via Email

Enter your Email


Powered by FeedBlitz

Quick Links