![]() |
|
Published 1998-08-01 Printer-friendly version
TopSpeed gives us three different ways to do lookups: falling-down-easy, a-tad-harder-but-more-customizable and tear-off-your-shirt-pound-your-chest-and-roll-your-own. But lookups themselves come in only two flavors. They are differentiated by where the lookup is called. Lookups can be called when a control is selected or when it is accepted. That's it.
If called when a control is selected, the end user has no choice but to select a record (or, if you decide to permit it, enter a new record and then select one) or cancel. Why does the user have no choice? Because as soon as the control is selected, the lookup is (immediately) called.
If called when a control is accepted, the user has a chance to make an entry first. In this case, the lookup only validates what the user entered. Some purists refer to the first case, when the control is selected, as a lookup and the accepted case as a validate.
While material to the end user (and it most certainly is), as far as you and I are concerned, the fact is that the primary difference is where the code is placed in the final source file. In the first case, the code generates into ThisWindow.TakeSelected for the control (ABC templates) or the control's Event:Selected (Clarion templates). In the second, it generates into ThisWindow.TakeAccepted or the control's Event:Accepted.
The code generated is, with only minor differences (as noted), the same in both cases.
!Code generated on Selected (in ThisWindow.TakeSelected):
ReturnValue = PARENT.TakeSelected()
CASE FIELD()
OF ?PEO:State
STA:State = PEO:State
IF Access:States.Fetch(STA:KeyState)
IF SELF.Run(1,SelectRecord) = RequestCompleted
PEO:State = STA:State
END
END
ThisWindow.Reset
END
RETURN ReturnValue
!Code generated on Accepted (in ThisWindow.TakeAccepted):
ReturnValue = PARENT.TakeAccepted()
CASE ACCEPTED()
OF ?PEO:City
IF PEO:City OR ?PEO:City{Prop:Req}
CIT:City = PEO:City
IF Access:Cities.Fetch(CIT:KeyCity)
IF SELF.Run(2,SelectRecord) = RequestCompleted
PEO:City = CIT:City
ELSE !<--- these are SELECT(?PEO:City)
!<--- not in the CYCLE
!<--- when selected END !<--- code
END
END
ThisWindow.Reset()
END
More importantly, the expected program behavior is exactly the same for both, and the point at which the call is made is not a relevant difference.
What exactly do we expect a lookup to do?
When there is no value in the control or when the value that is in the control is not valid, we expect the lookup to present a list from which the user may (must) select a value. "Not valid?" Not in the validation file. Whenever a valid value is in the control, we expect the lookup to do nothing - nothing at all, period.
Right click the control, select Actions, complete the three prompts. Along the way you may have to add the lookup file to your file schema. Total keystrokes: six if the file is already in the file layout, eight if it is not. That's pretty easy and you don't even have to type anything in, just make selections.
One important caveat: the Fetch method clears the record buffer. So, if you have begun an entry in the field, the lookup will not start at the closest record. Instead, it will start at the top of the file. Jon Waterhouse posted a template modification on 5 May 1998 to the topspeed.products.c4 newsgroup that you might want to check out (article title, "Less than optimal lookup behavior in ABC templates?").
The completed worksheets look like:
Actually, it can get even easier. How about no keystrokes at all? If you selected the "Must Be in File" option in the dictionary and created the app or the procedure with a wizard, this code will be generated for you. In fact, if you selected "Must Be in File," this code will be generated into the When Control is Accepted block for you whenever you populate the field on a window. Now, that is easy. (On the other hand, if you ever change the relationship between the files - a relationship is required by the "Must Be in File" option - you will get mysterious compiler errors all over the place. There have been a number of undesirable behaviors laid at the foot of this option, so be warned.)
Pros: easy to implement, behaves as expected.
Cons: difficult to place embedded code precisely. It is especially difficult to place embedded code after the call since the first available embed is after the host window is refreshed (re-displayed).
In addition, if you do place embedded code before or after the lookup, it will appear disconnected in the embed tree:
This is not a major problem but, looking at the embed list, it can be easy to forget that you have a lookup on the field.
Recommended use: lookups and validations where little or no embedded code is required. The overwhelming majority of lookups are handled perfectly by this method.
From the control's embed tree, select the embed you want to use: before or after generated code (for the control). Select the CallProcedureAsLookup code template and select the lookup procedure.
This template provides its own "embeds" for before and after the lookup call and if-canceled. This allows you to do, for example, a Select(?) to force the user back to the empty field.
Pros: While not quite as easy as the previous method, this way of doing lookups is still extremely easy. You can determine what is to happen if the user fails to select a record. You can copy over other fields from the lookup file. In general, you can fine-tune the behavior of the lookup with substantial precision.
Cons: Requires typing. This is no joke; should you make a typing mistake, you cannot edit it from the Embeditor or the embed tree. Further, that tiny little window (shades of CPD!) is not easy to work in. But, when you need fine control, this is the way to go.
Recommended use: When you need embeds immediately surrounding the lookup and before the host window is refreshed or when you have modest pre- or post-processing code, use this method.
When you need complete control (emphasis on "need")(emphasis on "complete"), nothing in the world beats doing it yourself (actually, there really is no other choice, is there?). Thankfully, the need to do so is rare.
Here is a situation I inherited, not once but twice, in the past few months. One file contains a FullName string and the file I want to use as a lookup contains LastName and FirstName separately.
Clearly, any standard lookup will fail when passing through the control. It will also fail during Non-Stop mode (i.e., when the Ok button is pressed and {Prop:AcceptAll} becomes true). If you do not turn off the Perform Lookup during Non-Stop Select option (on the Actions tab), you'll never get off the form.
Suppose we had a routine to parse the full name into LOC:FirstName and LOC:LastName. Then, we could place the following in either the control's Accepted or Selected embed (depending on the effect we want):
DO ParseName
CUS:LastName = LOC:LastName !Prime key fields
CUS:FirstName = LOC:FirstName
IF Access:Customer.Fetch(CUS:NameKey) !If no match
GlobalRequest = SelectRecord !call lookup
BrowseCustomers
IF GlobalResponse = RequestCompleted !write field back
FIL:Name = CLIP(CUS:FirstName) & ' ' & CUS:LastName
FIL:EMail = CUS:EMail
!any other assignments or computations
ELSE !otherwise,
SELECT(?) !re-select control
CYCLE
END
END
ThisWindow.Reset
(For you "old timers" out there, do you notice how very much like Clarion for DOS this code looks?)
Pros: You have total control over where, when and how your lookups occur. Typos can be fixed from the Embeditor or the embed tree. This code can also be placed in a global validation embed and, then, would only need to by typed once.
Cons: Lots of typing is required and you will not be able to use the FieldLookupButton template.
Recommended use: When nothing else will work, also when computations or a number of fields need to be copied, or even a conditional, second, lookup performed, this is it. When you want some non-standard behavior after the user completes or cancels the lookup, again, this is the way to go.
If you find yourself using this method frequently, you can make life much easier for yourself. Create a text file with the boilerplate code. That is, create a file with all of the code except the actual field and key names. When you need to do a lookup, import this file into the embed and just populate the field and key names (using the pop-up field list).
There is nothing in the world I hate more than having to create a procedure that duplicates another in all but a few functionalities. If I absolutely have to, that's one thing, but if I can make a few runtime adjustments, so much the better (we'll all spend hours figuring out how to avoid a few minutes of typing). Re-using an existing browse as a lookup is, relatively speaking, a piece of cake.
There are only a few things I might want to do: change the caption, disable some of the buttons and, perhaps, center the window.
While there is a Browse Preparation, Request to Select Record embed, I am long in the habit of using After Opening the Window (a/k/a WindowManager, Executable Code Section ... Init ... (),Byte with Priority "Last"):
IF ThisWindow.Request = SelectRecord
?BrowseWindow{Prop:Text} = 'Select Customer or Else!'
?BrowseWindow{Prop:Center} = True
END
! And, inside the Accept loop (never got anything else to work properly):
IF ThisWindow.Request = SelectRecord
DISABLE(?Insert,?Cancel) !Only "Select" is available
END
Well, there you have it. Three ways to do two kinds of lookups. If one of these techniques doesn't satisfy your needs, your needs are a lot more complex than anything I've seen.
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: $189
(includes all back issues since '99)
Renewals from $139
Two years: $289
Renewals from $239