This document leads you relatively gently, through an understanding of server views and how to add a new a Client Server Data View to an existing, sample Client Server application called HNDMTSSV.APP. This example server comes with a matching client application called HNDMTSCL.APP, to which you'll be adding also a client browse that accesses the data view you've added into the server.
A note about CHT application naming convention:
Applications like HNDMTSSV.APP with "SV" in the name, designate a (S)er(V)er application and applications like HNDMTSCL.APP with "CL" in the name, designate a (CL)ient application.
It may well, by now, have become obvious to you that "Client Server" applications come in pairs, a client part and a server part. Perhaps a more appropriate name for this class of applications might be Client/Server, or better still, Client and Server.
The client part consumes data (edits, deletes, adds, reports and otherwise processes records) while the server part delivers data packages up to the client and receives add, change, delete, process instructions and data back from the client.
A client, can of course, ask for the entire data set in a single table or in a group of tables comprising a data VIEW (the way a generic ABC browse does), by not requiring a query, but that's just really bad design that in the context of high traffic and large-data tables makes transactions across the WEB or WAN slow at best and next to impossible at worst.
A Client Server data VIEW consists of either one table, or more frequently, several joined tables that are scanned and processed together. To a remote client application consuming such a VIEW, the view behaves and feels like a single, flat-file, even when the server VIEW itself is constructed of several joined tables in the back end.
To obtain data from a client server VIEW, the remote client application sends a query to describe, and limit the information required from the server. Queried information comes up to the client as structured XML representing records and fields read from server-side data tables. This information is unwrapped from XML and put back into standard Clarion record structures or group stuctures by the CHT client and is displayed client-side in a ListBoxBrowseExtender-like browse, or HandyMarkerBrowse-like browse based on a memory queue. No local data tables need to be present for this to work.Any time that a new client query is posted to the server, the previous memory queue's contents are free'd and only records matching the latest query are inserted once again into the list-box queue.
When records are changed or added these are posted back up to the server and are stored there, and refreshed from there. Hence multiple Client applications can be working on the same server's data tables all adding, deleting, updating, reporting and processing simultaneously. All clients are seeing the same shared set of server-side data tables and are updating them in real time.
This send a query to get matching information approach
is typical of SQL data transactions. Our server VIEWs are modelled after
SQL's client-server design, even more particularly, on its
Any data request sent up to the client from such a VIEW, includes only the specific fields that have been designated in the server VIEW design. And they are delivered to the client in context of the ACTION or OPERATION currently requested by the client. Typical ACTION contexts are: Browse, Form, Process, Report and Preview. Of course, user-custom actions are also possible and can be handled this way, although custom operations are less frequently required in day-to-day data transactions.
The knowledge that a query must travel up to the server aimed at a specific view,
in the context of some client-side action to take place, one quickly gathers that
when posting a data request, the web client, communicates to the server several
things at once:
• -- A CHT QUERY STRING to describe the data requested from the server
• -- A SERVER VIEW NAME to be accessed for the data being requested
• -- A NUMBER, representing the client-side ACTION or OPERATION being performed
Create a new VIEW NAME on the main server procedure SecureHTTPDataClientServerMain() by attaching extension template ClientServerQueryBrancher_BIC (next three images).
Note that when you see a suffix on one of our template names, such as _BIC above, that indicates the template is a child of another template which must be there before the suffixed template can be attached to the procedure. In this case ClientServerQueryBrancher_BIC is a child of "(B)rowserServer(I)nitialization(C)controls"
Create a new back end procedure (like V_ClientServerMembersView() and V_ClientServerMessagesView()) with procedure template HandyJDOAdapter. This new procedure will service the "CUSTOMVIEW" name that you just added above. HandyJDOAdapter is a CHT procedure template that in combination with another CHT extension template called ClientServerDataBuilder will answer queries from your client application, whenever they come addressed to the view with the unique name CUSTOMVIEW.
A built-in server procedure called SERVER.TakeQueryBrancher() directs client requests addressed to CUSTOMVIEW into the procedure you are about to hook onto this example server.
Fill out the data file tree for this new procedure. Make sure to include the SYSID Key of the primary VIEW table in your file tree settings. This is a mandatory setting, as the template uses the SYSID field embedded in that key, to determine the procedure's primary "Fetch Filter". (See next image)
Once the file tree is correctly configured as shown, then visit the Right-Click -> Properties -> Actions area of your VIEW procedure to set the file opening behaviour. To obtain your correct settings for the dialogs here, just follow the settings we have given on the two example "V_" (or VIEW) procedures. (see next image)
Note that some buttons, particularly "Files" and "Generate", may appear to be disabled until you save your new procedure and generate the application. No need to compile. It's too early for that. Just save back to the procedure tree, and from there, click the nearest generate button or menu. Doing that causes our template to execute some internal code that allows for further configuration to take place.
The procedure's property dialog looks like the one in the image below when you return to it after saving and generating. For the purposes of this exercise, use the same settings on the dialogs under the "Files" and "Generate" buttons as already present on the existing two server VIEW procedures.
Add the extension template ClientServerDataBuilder to your new VIEW procedure and complete the data fields dialog. Importing fields en masse from the file structure, is OK. However, since each field has some "client rights and privileges" information that must be completed, its often easier to add fields to the VIEW one at a time, while adjusting the required settings for each as you go. These settings control which client ACTIONs (Browse, Update, Change, Process, Report, Preview) may be performed on any requested field.
Ultimately, at this juncture, you're affecting the entire functionality available to the client application with the settings applied now. It's best to get them right at the outset, or you'll be back here having to make adjustments. Client server applications are built in pairs and its the server part, the part you're working on presently, that calls most of the shots.
By default, the template assigns the broadest rights to all fields, like REG:Name below, and makes them available for all client ACTIONS or OPERATIONS, which is probably not what you're going to want, at least on some security-critical fields. Note the difference in settings alone, between REG:Name and REG:Last.
Again, check the example procedures provided.
Don't forget as you add fields, that you can include multiple, joined tables into the VIEW. You may even include local server variables that you set via hand-embedded code. These may be operated on by the client side as if they were actual fields located in the data tables.
This is EXACTLY how an SQL VIEW behaves. Our CHT design is strongly modelled on the design of SQL VIEWs.
Fields and variables sent from here can even be transformed (custom formatted) on the way up and deformatted on the way back into the data tables.
EXPORT your fields from here (as shown), and you will save yourself a lot of time when it comes to incorporating this VIEW into your client application as a browse, form, process or report.
It is important to understand all of the dialogs presented by the ClientServerDataBuilder template pictured below. Some other key dialogs not already discussed, are: FetchFilters, Query Keywords, Query Size Limits, and Session Field .
Considerable instructional text is provided on each dialog to assist with understanding. As a minimum, follow our lead on the two example procedures in HNDMTSSV.APP, and please do endeavour to learn more about what these various dialogs do by reading the explanations provided as you open them.
We'll cover briefly each of the "other" dialogs, numbered below.
"Client Server View" is a mandatory dialog. This is where you make a programmatic association between the CUSTOMVIEW name established on the main server procedure, and the ClientServerDataBuilder template on your data packaging procedure.
Our built-in procedure SERVER.TakeQueryBrancher() uses the information from this dialog to route all client queries directed at "CUSTOMVIEW" to be processed by your new V_CustomView() procedure.
On the "Table Action" dialog you will manage in broad terms how your table is configured for INSERT, UPDATE and DELETE rights from the client. Here, at the table level alone, you can determine whether a table allows records to be inserted, changed or deleted by the client. At field configuration level you can only override or revoke rights granted here, not grant them.
Take note that this dialog is mandatory, tables missing from this dialog will permit no ACTIONs at all to be performed on them.
The number 3 dialog, Query Size Limits is not mandatory in the sense that you must configure it or risk the server won't work. This dialog is pre-configured by our template to limit the record set resulting from any query, good or bad, to 150 Records. To take control of it, you will need to insert either a hard value as we have done, or a variable read from the server configuration file.
Don't forget to also indicate how the server should behave when the record-limit for any query has been reached. The choices are to return an error or to simply return the number of records allowed by the limit. Our procedure implements the second option. When the second option is used, no error is raised, so how then can the client user know that his/her query was too broad and exceeded the record limit?
For the answer to that, keep reading and look under item 7 pertaining to the "Footer Data" dialog to see how we chose to handle the situation.
The number 4 dialog, Query Keywords, is used to "teach" this server procedure about CHT Query Language. You've seen this dialog before in lots of places. In this case, it allows us to send CHT Query Language queries from the client and guarantees that the procedure we're addressing via the "CUSTOMVIEW" identifier, can understand what's being requested. The defaults for this dialog, if implemented similarly on both the client-side query control and here, server-side, will give you full query capability using default CHT Query Language syntax.
We've configured the Query Keywords dialog for default settings using internal keywords and macros.
These are exercised by the
But that's a whole other story that you can read about elsewhere. Here, we've chosen simply to use the default HNDFBACK.TRN values as we will do client-side also.
Number 5 dialog, Session Field, is not a mandatory item either. Session Field is most useful on a busy server where multiple individuals have change-rights to records in your VIEW's data table(s).
So, should User A open a record for editing from client Location A, while User B opens the same record for editing from client Location B, the first user to save the record back wins.
If, say, User B were to edit and save the record back before User A pushes her save button, then, on pushing "Save", User A will get a warning message to the effect that the record was changed by another user "Session".User A will need to cancel her changes and reload the record to pick up User B's changes and edit forward from these.
The sample data table NGMember that we're incorporating as one of the two tables in our CUSTOMVIEW design, contains a field designated as the Session Field. Select this field into the Session Field dialog.
It is important that the selected session field is incorporated into the ClientServerDataBuilder template's "Config Data Fields" dialog and is given default rights to travel between client and server for all operations (see image 5B, below).
Export this field to be used by the client application along with all other data
fields from the "Config Data Fields" server dialog as outlined at
Dialog number 6, Fetch Filters provides the client-side developer with a server-side-defined "filter" that uniquely identifies a record set from any of the tables in the VIEW. Good design dictates that every table in a view has a "SysID" or "Unique ID". These "Fetch Filters" can be used for cross-priming fields or simply to recall a particular record-set using the ID of one of the tables in the VIEW as the unique identifier.
Dialog number 7, Footer Data provides an opportunity to insert some information about the data set being returned after the data for that query has been accumulated server-side. Every returning "data package" has only one footer area.
As mentioned earlier, in this CUSTOMVIEW example, we're using the Footer Data to return information about whether max-record limits have been reached by the current query. We've inserted a local variable into which we have programmatically inserted information about each query as the query completes. This information tells the client operator whether the record count for their last query was over or under the max-records limit configued on the server.
Obviously, savvy users can use this information to narrow their queries whenever they are informed that their last query went beyond the defined record-count limit.
The following limit illustrates code from the server to show how the variable RecordMaximumReached is primed with a value that reflects the outcome of the last browse query.
There are other dialogs on the ClientServerDataBuilder template that you should know about, read about, and put to use where appropriate. We repeat here, that considerable instructional text is provided on each dialog to assist with understanding. As a minimum, follow our lead on the two example procedures in HNDMTSSV.APP, and please do endeavour to learn more about what these various dialogs do by reading the explanations provided as you open them.
You will progress faster in your understanding and skill level, by doing and by applying some hands-on experimentation than by sniffing around and never taking the initiative to build something.
This section of our paper discusses HNDMTSCL.APP, a client application that accesses several data VIEWs published by server application, HNDMTSSV.APP, discussed above.
The client application already contains two browse procedures RequestMembersBrowse() accessing a server view called "MEMBERSVIEW" and RequesetMessagesBrowsePopFav() accessing a server view called "MESSAGESVIEW". Here, we will be adding a brand new web browse procedure called CustomViewBrowse() which provides access to a back end view called "CUSTOMVIEW", newly created on the server.
To add a new procedure, click "New Procedure" and and call it "CustomViewBrowse".
For the next part of this first step, select a template called HandyWebBrowseProcedureEx(HNDTools). Note that there are two templates with a similar name, one ending in "Ex". Though either of these templates will work for this example exercise, we'll be using the "Ex" version since it implements a newer and "snazzier" query control called PopFavoriteQueries_CSBL, the "CSBL" part of which stands for Client Server Browse ListBox.
Clicking through and selecting that HandyWebBrowseProcedureEx template brings you to this window as the procedure is created.
While we're on this topic of selecting a CHT procedure template that is going to create a default window design with a roughed-in browse on it, please be aware that you must select any and all of CHT's procedure templates from the IDE's Defaults tab (as illustrated above). Selecting the "Defaults" tab ensures that the legitimate default values provided by our templates are inserted as required.
Now that the new procedure is in place, we need to configure it in various ways. The first thing it needs is a list of "tables", including both remote and local tables. A Clarion application of this sort, despite the fact that our templates help to make it into a Web Client which can access remote server data, can also access local data. You're presented with a familiar "Tables" dialog into which are inserted the required tables, regardless of whether they're local or remote. This dialog is identical to the "Tables" dialog presented for a vanilla Clarion browse, so there should be nothing unfamiliar here.
Once the tables are inserted and the procedure is saved into the application, it appears in the procedure tree near the bottom, ready to be hooked to a button or a menu from which it can be started. Before we do that however, let's visit the "Extensions" menu and view the "Properties" tab.
Here on the "Properties" tab, of the "Extensions" area of the procedure, note the procedure prototype is written as follows:
CustomViewBrowse(HNDClient xHttp). The "HNDClient xHttp" value must be passed in from the calling procedure.
This passed in value is actually the same HNDClient class that on the login procedure logs into the server and maintains
an open connection to it. This procedure uses that passed-in HNDClient Object to communicate with the server and request data
from it over a secure, and optionally compressed and encrypted channel.
Now that we have that important bit of information, concerning a passed-in HNDClient Object we can hook this CustomViewBrowse() procedure into the application's login window. The "hook-up" could be a menu or a button. We're using a vanilla Clarion button control dropped on the toolbar of the login window.
Right click this button to be taken to the button's template prompts for adding functionality. We've selected "Call a Procedure" and selected our procedure CustomViewBrowse into the dropdown. Leave the thread button unchecked, since Clarion cannot natively START a procedure on a thread if you're passing it anything other than a string. Though there are lots of ways to overcome that mundane limitation. Even this application does that, if you care to look for the embedded code under any of the other buttons.
For the purposes of this exercise, however, we'll employ the more ordinary, no-embed-required, technique of hooking up the procedure and running the procedure unthreaded. Note that in the "Parameters" box we've inserted (HTTP). This is an instance of the CHT HNDClient class on the login procedure which we've arbitrarily called "HTTP".
This is a step in the construction of the application where a developer might trip up unless he/she knows how to determine what the HNDClient class instance has been named on the login procedure. We can tell you that "HTTP" is not it's default instance name, because all template-instance names have a number in them, representing the instance number of the template populating the class. We'll leave that little mystery with you to sort out, by having you hunt down the "Classes" tab of a template called EmbeHTTPClientFunctions in the login procedure.
Clicking "OK" on this dialog saves the entered settings and brings back the procedure tree pictured in the next image.
In this next step we can begin to configure the Clarion List Box Control provided on the new browse procedure's window by our procedure template. From the procedure tree, click the CustomViewBrowse procedure followed by the "Extensions" button. Then double-click the ClientServerBrowseListBox template visible there. Doing that, displays the following template dialog.
We've highlighed the "List Box Queue" prompt above, because in this fourth step we'll show you how to populate fields into the list box queue by importing the fields we earlier exported from our server application at the time we constructed the V_CustomView() procedure that provides us with a server data view named "CUSTOMVIEW".
This assumes you exported the server fields being delivered from the "CUSTOMVIEW" procedure installed on the server. If you didn't export the view fields at the time that the server procedure was constructed, you can always go back to the server application, and perform the export, and then come back to this client application.
Once you know that the export file is there, click the file dialog button indicated in the image below and navigate over to the /accessory/hnd/vardump/ directory below your Clarion installation.
Our sympathies at this point go out to the typing fingers of any developers who fell into the trap of installing Clarion into its default, deeply nested directory instead of something short and simple like c:\c10\ as we've suggested since the appearance of Clarion 7.
Look for any .TXT files in this /accessory/hnd/vardump/ directory. The file we want contains the name of our server procedure V_CustomView() as well as the name of the view being served: "CUSTOMVIEW". Select the file and click "Open". That brings the fields back and inserts them into the queue fields list for CUSTOMVIEW.
In the image below, you see the fields that were imported from the text file displayed above. Note also that the name of the back end view (i.e. CUSTOMVIEW) came along with the fields.
It's critical that any client web browse procedure of this type knows which back end view it must communicate with. That's also why we stressed in the server portion discussed earlier, that back end view names must be unique. We use the convention of entering these in UPPER CASE to help us identify them as back end views. That's not essential, just a mnemonic that helps jog the memory.
In step four above, we imported fields from our server's back-end view design templates. These were all the field names as configured, server-side, for all possible ACTIONS or OPERATIONS (Browse, Form, Process, Report, and Preview) that might be performed client-side.
Our client-side procedure, with the assistance of the query control, PopFavoriteQueries_CSBL, will send queries to the server VIEW and return the field values from our tables, as allowed and configured by the business rules imposed on that server-side VIEW. This queue is shared by all functionalities available on the client and is for all intents and purposes a "memory file" version of the table records matching the query sent to the server.
You'll remember, though, that not all fields in the server VIEW were designated for display in the browse list box control. Our template knows from the data you imported in step four, which fields are destined to be displayed on the browse and will help you create code for a legal browse LIST() format string that reproduces that browse fields list for you on the Clarion list box control which our template auto-populated on the procedure window.
Here, then is how you create or export that list box format string to the /accessory/hnd/vardump/ area where you can pick it up and insert it into your window.
We say "create or export" above, because, in addition to the browse format string being exported into a file marked at both ends by "XXXX", the format string is also placed onto your Windows Clipboard. So as long as you don't do anything that disturbs the contents of your clipboard, and go directly to the listbox formatting step from here, you can dispense with opening the XXXX file and proceed to directly replacing the browse LIST() format string with the one now on your clipboard.
Note, that you don't have to format the list box using either of these copy/paste methods. If you decide not to, you can always insert the fields manually, one-by-one from the file tree, the way you do on an ABC browse.
On the other hand, you've already decided by building a well though out server VIEW procedure, which fields should be on the browse, you might as well take advantage of that work already completed and produce a file and/or clipboard entry using the dialog illustrated in the next image.
We will pick up the construction of the list box control shortly, first though, in this step six, we'll briefly review other dialogs on the "ClientServerListBox" template while we're here. We won't go into these in any detail, since that's probably better handled by a video, that we now have in production to expand on this document. Here, then are the other dialogs that should be visited and configured, or not, as determined by the specific requirements of your web client application. Most dialog default settings are pre-set for the most likely developer specifications.
The template dialogs are well documented so you should be able to navigate and complete these, where required. We've marked or illustrated the dialogs that are most important.
Note that the "Suppress Open" dialog is not optional. You must insert here the names of any tables that are not "local" to the client. Local tables, as the name suggests, are tables that store information client-side, as any standard desk-top application does.
Remote tables, on the other hand, are tables which do not need to be "opened" client-side, where the client application is running. These tables are sitting server-side, where the server application is running. Data "bundles" matching client-initiated queries, are dispensed from the server to the client over an HTTP connection shared between them.
We've inserted NGMember, and NGMemberExtra here, as these are the server-based tables that we'll be browsing across the web. Another table inserted into the table "tree" earlier, called PreQueries does not need to be inserted here. This PreQueries table holds user-entered queries and since these values are specific to the user of any given client application instance, running here, these stay with the client.
It doesn't necessarily have to work that way in every client design, but that's how we've chosen to do it in this particular example.
The ClientServerListBox template dialog marked with number 2, called
Query Prebuilts is configured here merely as a fail-safe
setting and is not mandatory. With this configuration, we're hard-coding
an opening query for our browse, in the event the query file we've organized in
The ClientServerListBox template dialog marked with number 3, called Modify Column Carets is also not mandatory as it comes with a default configuration. Specifically, here, we've pushed the CLR button as per the template interface documentation. That clears the default caret characters which normally surround the header of the current sort column. We've also inserted double left/right arrows to indicate ascending or descending sort order by pushing the template buttons to select them.
Another not mandatory dialog is ClientServerListBox template dialog number 4, called Browse Control. If you have imported your fields from the server, the queue name is formed automatically for you based on the server VIEW that's being addressed.
You shouldn't need to touch this dialog unless you know what you're doing and want to over-ride some template defaults. Otherwise, default names and values will apply and work as expected.
ClientServerListBox populates a number of button controls, "Raw", "Thread", "Recall", "Reply" which in some cases do not apply. They're there in case you need them. When any given button is not required by your specific implementation, simply check the switch beside it and it will disappear at run-time.
There are other dialogs on this ClientServerListBox template that you should know about, read about, and put to use where appropriate. We repeat yet again, that considerable instructional text is provided on each dialog to assist with understanding. As a minimum, follow our lead on the two example browse procedures in HNDMTSCL.APP, and please do endeavour to learn more about what these various dialogs do by reading the explanations provided as you open them.
You will progress faster in your understanding and skill level, by doing and by applying some hands-on experimentation than by sniffing around and never taking the initiative to build something.
Creating a Client Server web browse with our HandyWebClientBrowseProcedureEx template, incorporates some other CHT templates. One of these, ClientServerListBox, we've already covered at some depth above. Two other templates that come along with the procedure are ClientServerBrowseBuilder_XCL and PopFavoriteQueries_CSBL. We will touch on those briefly in this step-by-step outline.
Remember that the "export" function step just performed above, at the same time places the required listbox LIST() format string on the Windows Clipboard. We've illustrated here the longer process of re-obtaining that string from the export XXXX file, but you may not need to do that, if the clipboard is not disturbed between the time the "export" was performed and window code is opened as illustrated below.
Some of the templates mentioned above are control templates which manage window controls such as the List Box Control in which queue data is displayed. The browse format string exported in the previous step can be incorporated wholesale into the code for this listbox. With little effort on your part, this populates the columns designated by your server view procedure onto the browse, via the listbox format string. Other columns and fields, not slated to be on the browse do not appear in the list box format string.
Start this operation by right-clicking your new web browse procedure from the application procedure tree and selecting "Properties". From this properties tab, click the ellipsis button beside the "Window" button on the right. This drills down into the window design as it appears in code.
Listbox-specific code is identified by code that starts with
Our template places it last in the window design code before the
statement. Replace the code that you find here with the code provided in the export
From Clarion's File menu navigate once again to /accessory/hnd/vardump/ and open the file name that contains both the name of your application, the name of the procedure you're working on and the name of the server view being serviced. Listbox format string files are readily identifiable because they start and end with four "x" characters. The one we want in this case is illustrated in the next image.
Once the file is open, copy the entire line of code found there onto your Windows Clipboard (HINT: it may already be there) and bring it back with you to the window code area as shown in the next image. Then copy/paste the new listbox format string over the original generic one populated by our template.
The new format string appears on one line of code. Don't fret about trying to line-wrap this code. Saving and exiting the window code editor does that for you.
After saving and closing the window code editor and then re-opening it, you will see the list box format string properly line-wrapped. The next image illustrates that for you.
Note that the fields designated to be displayed in your web browse are incorporated into the format string along with data pictures and column header names. Any further work on customizing the browse can now be carried out from the Clarion window designer.
In the window designer, pictured below, lay out your columns and adjust list box size to the window as you would any other browse list box. You can, of course, change data pictures and column headers at this stage if you like, but remember that in the back end you designated these variables to be searched for with KEYWORDS which are now your column headers.
It is a CHT convention to use the column headers (without spaces) as the column query keywords. Column headers, by the way, do not need to be in upper case, so feel free to pretty those up if you wish.
At this stage, you should be able to successfully compile and run your procedure. But there's one important step still to complete. That is, the query template PopFavoriteQueries_CSBL.
A web browse procedure of this sort, works like an SQL data grid. When a query is submitted to a server-based VIEW, the view returns just the data matching the query and populates the data grid. This Clarion browse has similarly been configured to provide data only when there is query submitted.
Clarion's default behaviour, which entails filling a browse with data when there is no query to describe the data, has no place in an across-the-web browse. Further, if you remember during our CUSTOMVIEW design steps back on the server, we placed a limiter on the number of records that the server would send for any given query. That limiter is adjustable but it protects your server from being brought to its knees by bad or careless queries. The server can be configured to either return a limited number of records when the query is too broad, or to return an error indicating the uses should narrow the data request.
To configure the query template, just double-click it. Select first the "Pre-Built Queries" item and configure that dialog as shown in the next image. The other configurations follow in separate images each of which illustrates completely what needs to be done.
You are now ready to fully, generate, compile and run your application, in order to test your new Web Browse Procedure.
Note that there is not one single line of hand-embedded code used in this client web browse procedure. Neither is there a single line of hand-embedded code in the back end client server's CUSTOMVIEW procedure.
Hand coded customizations, of course, are possible and highly useful once you get the hang of how, why and where these embedded embellishments might be placed. That knowledge will come with time, after you've built and experimented with several client/server combinations, like these two example applications HNDMTSSV.APP and HNDMTSCL.APP.
Here then, is the running browse displaying some records. It's time to take a breather and make some cosmetic changes to our browse window, before we take the plunge into adding an update form, a process and a report.