A Better Way To Write Pick/Browse Lists [3/4]
Posted in 1993
The most important words in a documenter's vocabulary are : for example. Here is some example 4gl code that uses the gvs (general view screen) system. When entering a referring or consulting physician, a secretary may enter part of the name. 4gl then finds the doctors matching that name, presents a pick list, and allows the user to select the correct physician. For internal doctors, present the department (primary appointment). For external physicians, present the city and state. This additional information helps identify the correct doctor unambiguously. include(gvs_macros.i) { m4 acts as our preprocessor } { now define a static record, where the rows from the cursor will be fetched } { This record handles both views, thus it has department and city/state. } DEFINE docview RECORD doc_serial_id INTEGER, doc_lname CHAR(20), doc_fname CHAR(15), doc_city CHAR(20), doc_state CHAR(2), doc_dept char(3) END RECORD { invoke the macro that expands into support functions for this record } general_view_screen(docview) { Function to format lines on the screen, for external doctors } FUNCTION docext_line(idx) DEFINE idx smallint { index, as in fetch absolute ... } if not idx then { idx = 0 activates initialization code } let gvs_title = "External Doctor Lookup" let gvs_format = "20|15|20|2" { width of each field } { headers for columns } let gvs_hdr3 = "Last Name|First Name|City|St" let gvs_hdr4 = "-" { generates dashed line, like tbl macros in nroff } else let gvs_line = docview.doc_lname,"|",docview.doc_fname,"|",docview.doc_city,"|",docview.doc_state end if { return the color of the line, whether that item is preselected, and whether it involves supplementary free text input } return "n", "N", "N" END FUNCTION { the corresponding function for the view of internal doctors } FUNCTION docint_line(idx) DEFINE idx smallint if not idx then let gvs_title = "Internal Doctor Lookup" let gvs_format = "20|20|6" let gvs_hdr3 = "Last Name|First Name|Dept" let gvs_hdr4 = " -" else let gvs_line = docview.doc_lname,"|",docview.doc_fname,"|",docview.doc_dept end if return "n", "N", "N" END FUNCTION { If a doctor is called up by maiden name or alias, show the real name } FUNCTION doc_curname(idx) DEFINE idx smallint DEFINE fullname char(100) call doc_get_name(docview.doc_serial_id) returning fullname display "current doctor name is: ", fullname clipped return 0 END FUNCTION { A code fragment to find an internal doctor whose name starts with Rose } LET gvs_selstring = "SELECT doc_serial_id,fast_lname,fast_fname,", " '','', aa_dept", " FROM sis_fast_prs,sis_doctor,outer sis_att_appt", " WHERE fast_group = 40", { attending physicians } " AND fast_lname MATCHES 'ROSE*'", " AND doc_prs_id = fast_prs_id", " AND aa_doc_id = doc_serial_id", { link to department } " AND aa_appt_type = 'P'", { primary appointment } " ORDER BY fast_lname,fast_fname" CALL gvs_start("docview", "docint_line", { function that constructs display line } "", { PICK_MANY select function, not applicable here } "f6=doc_curname", { functions to call when fkeys are pressed } COLUMN_1, PICK_ONE, WAITMSG_OFF) RETURNING anerr, idx return docview.doc_serial_id { the following fragment generates a picklist of matching external doctors } LET gvs_selstring = "SELECT doc_serial_id,fast_lname,fast_fname,", " add_city,add_state, ''", " FROM sis_fast_prs,sis_doctor, outer sis_address", " WHERE fast_group = 50 ", { outside physicians } " AND fast_lname MATCHES 'ROSE*'", " AND doc_prs_id = fast_prs_id", " AND add_entity_id = fast_prs_id", { link to address } " AND add_entity_type = 'P'", " AND add_type = 'BP'", { business primary } " ORDER BY fast_lname,fast_fname" CALL gvs_start("docview", "docext_line", "", "f6=doc_curname", COLUMN_1, PICK_ONE, WAITMSG_OFF) RETURNING anerr, idx return docview.* Roughly 100 lines of code. How much code would be required to write the corresponding input arrays and cursor control, and how much standardization and maintainability is lost when each developer writes and rewrites these various lookup screens? In our experience, each view converted saves around 300 lines of extremely complicated, unmaintainable, unreadable code (input arrays and onkey nextfield gotos, and the like). It also eliminates a form file, which turns out to be very important. Changing running software and a form file simultaneously is almost impossible. We cannot ask our users to all log off. If we install the new software with the old form, the next user on is in trouble. If we bring in the new form the current users are in trouble. We have developed an awkward way around this problem, slightly better than renaming the forms with each software release, but under gvs you can change field sizes, colors, positions, and contents without worrying about forms at all. Every line becomes a string and these strings are displayed to the common form that never changes. The gvs system is flexible as well as maintainable. Rows or fields within rows can be displayed in different colors, under the user's control. User functions can update the lines in the display, or force the select statement to start again, if the list itself has been changed. If you have 4gl-gx, or if you replace the low level gvs screen functions with an X-term equivalent, you obtain a proper GUI interface, almost for free. If you have room for a two column picklist, simply pass COLUMN_2 instead of COLUMN_1. If you change your mind, and need to select multiple items from an established list, simply replace PICK_ONE with PICK_MANY, and supply a selection function that takes appropriate action for each item selected. (This usually entails inserting a row into a table.) Additional capabilities are under development. -- I do more all day -- than most people do before 9 A.M. Karl Dahlke eklhad@siss81.sis.rpslmc.edu (312) 942-8040