Exercise 6: Second Lookup Class
Click here to return to the exercise description in the main part of the tutorial.
-
Using VS Code - ObjectScript, change the top line of ObjectScript.Lookup1 to ObjectScript.Lookup2, and save it as ObjectScript.Lookup2.
-
The CurrentCount() method makes use of the BitMap-ID index. Since it contains a 1 bit for each person, a loop through the chunks and $BitCount is all you need.
Class ObjectScript.Lookup2 { /// count the "1" bits from the chunks of the Bitmap-ID index ClassMethod CurrentCount() { set records = 0, chunk = "" for { // use the 3-argument $order to get the next chunk and the bits stored there set chunk = $order(^PersonI("Bitmap-ID", chunk), 1, bits) quit:(chunk = "") // add the "1" bits to the count set records = records + $bitcount(bits, 1) } write !, "There are ", records, " records in the database." } }
-
Modify Main() so that it calls CurrentCount(). The new ElseIf for phone lookups can go anywhere; the example below places it above the ElseIf for date of birth lookups.
Class ObjectScript.Lookup2 { /// main loop section, dispatch to different methods based on user input ClassMethod Main() { do ..CurrentCount() while ..GetInput(.type, .search) { if (type = "help") {do ..Help()} elseif (type = "phone") {do ..Phone(search)} elseif (type = "dob") {do ..DOB(search)} } } }
-
Modify GetInput() so that it also handles phone numbers. Since ValidDOB() writes an error message, date of birth lookups should be handled last. That's why the new ElseIf for phone lookups is placed above the ElseIf for date of birth lookups. The Else writes the final part of an error message (the comment explains the hack).
Class ObjectScript.Lookup2 { /// prompt user for a lookup string, return search type and search string ClassMethod GetInput(output type as %String, output search as %String) as %Boolean { read !, "Lookup: ", lookup return:(lookup = "") 0 // user entered nothing so return FALSE if (lookup = "?") { set type = "help", search = "" } // the RegEx accepts ###- or ###-###-#### only elseif $match(lookup, "\d{3}-(\d{3}-\d{4})?") { set type = "phone", search = lookup } elseif (##class(ObjectScript.DataEntry4).ValidDOB(lookup, .convdate)) { set type = "dob", search = convdate } else { // this is a hack for invalid input // ValidDOB() writes an error message, and the text below gets added to that write ", name, or phone" set (type, search) = "" } return 1 } }
-
The new Phone() method handles exact matches first, and then area code matches.
Class ObjectScript.Lookup2 { /// lookup phone or area code ClassMethod Phone(phone as %String) { set count = 0 // handle exact match first set id = $get(^PersonI("Phone", phone)) if (id '= "") { set count = 1 write !, "1) " do ..DisplayLine(id) quit } // handle area code matches next elseif (phone?3n1"-") { // use 3-argument $order to get first matching phone number and its ID number set ph = $order(^PersonI("Phone", phone), 1, id) // loop through matching phones, and number them while ($extract(ph, 1, $length(phone)) = phone) { write:(count = 0) "...finding area code matches" set count = count + 1 write !, count, ") " do ..DisplayLine(id) // use 3-arg $order to get the next phone number and its ID number set ph = $order(^PersonI("Phone", ph), 1, id) } } if (count = 0) {write "...no matches"} } }
-
Modify Help().
Class ObjectScript.Lookup2 { /// display lookup options ClassMethod Help() { write !, "You can enter:", !?10, "* date of birth", !?10, "* full phone number or area code only ""617-""", ! } }
-
Click the Save and Compile buttons.
-
Start the Terminal, and run your method to test the new Phone lookups, by typing do ##class(ObjectScript.Lookup2).Main().
-
Modify Main() again, adding another ElseIf for name lookups.
do ..CurrentCount() while ..GetInput(.type, .search) { if (type = "help") {do ..Help()} elseif (type = "phone") {do ..Phone(search)} elseif (type = "name") {do ..Name(search)} elseif (type = "dob") {do ..DOB(search)} }
-
Modify GetInput() again to handle names. Again, the new ElseIf is placed above the ElseIf for date of birth lookups.
if (lookup = "?") { set type = "help", search = "" } // the RegEx accepts ###- or ###-###-#### only elseif $match(lookup, "\d{3}-(\d{3}-\d{4})?") { set type = "phone", search = lookup } /* the $zconvert converts the last name and first name entered to Last,First format the pattern match accepts Lastname only, or Lastname,Firstname */ elseif ($zconvert(lookup, "W")?1U.L.1(1","1U.L)) { set type = "name", search = $zconvert(lookup, "W") } elseif (##class(ObjectScript.DataEntry4).ValidDOB(lookup, .convdate)) { set type = "dob", search = convdate } else { // this is a hack for invalid input // ValidDOB() writes an error message, and the text below gets added to that write ", name, or phone" set (type, search) = "" }
-
The new Name() method is the longest method so far. It uses a focused $Order loop for last names. For each matching last name, a $Order loop either goes through all first names for that last name (if only last name was entered by the user), or it's a focused loop for the first names that match. For each last name and first name, a final $Order loop goes through all the ID numbers.
Class ObjectScript.Lookup2 { /// lookup names in these forms: Smith; Smith,John; Smith,J; Sm,John; Sm,J ClassMethod Name(name as %String) { set count = 0 set last = $piece(name, ",", 1), first = $piece(name, ",", 2) // last may be an exact match, so find preceding last name set ln = $order(^PersonI("Name", last), -1) // loop through last names for { set ln = $order(^PersonI("Name", ln)) // quit as soon as last name doesn't match original quit:($extract(ln, 1, $length(last)) '= last) // first may be "". Otherwise, it may be an exact match, so find preceding first name if (first = "") {set fn = ""} else { set fn = $order(^PersonI("Name", ln, first), -1)} // loop through first names for { set fn = $order(^PersonI("Name", ln, fn)) // quit as soon as first name doesn't match original, or is "" quit:(($extract(fn, 1, $length(first)) '= first) || (fn = "")) set id = "" // loop through all IDs for { set id = $order(^PersonI("Name", ln, fn, id)) quit:(id = "") write:(count = 0) "...finding name matches" set count = count + 1 write !, count, ") " do ..DisplayLine(id) } } } if (count = 0) {write "...no matches"} } }
-
Modify Help() again.
Class ObjectScript.Lookup2 { /// display lookup options ClassMethod Help() { write !, "You can enter:", !?10, "* date of birth", !?10, "* full phone number or area code only ""617-""", !?10, "* full name: Smith,John", !?10, "* last name: Smith", !?10, "* partial name: Sm,J or Smith,J or Sm,John", ! } }
-
Click the Save and Compile buttons.
-
Start the Terminal, and run your method to test the new Name lookups, by typing do ##class(ObjectScript.Lookup2).Main().
-
You'll use a matches array to keep track of the matches so that the user can select one for display. Remember that you don't have to declare anything about this array; you just use it. You need to add four lines to your code. In DOB(), add a line between Quit and Write.
quit:(id = "") set matches(count) = id // keep track of matches write !, count, ") "
In Phone(), add a line in the "exact match" If, between Set and Write.
set count = 1 set matches(1) = id // keep track of exact match write !, "1) "
In Phone(), add a line in the "area code matches" ElseIf, between Set and Write. And in Name(), add the same line in the innermost nested For loop (ID numbers), between Set and Write.
set count = count + 1 set matches(count) = id // keep track of matches write !, count, ") "
-
Now that your code is building the matches array, you need to write a Select() method, that takes the array as an argument, and returns the ID number of the user's selection. Notice that the code doesn't check that the user enters an integer, nor does it check if the integer is in the range of 1 to count. It simply uses $Get to check if the user's choice is an existing subscript in the matches array, and returns the corresponding ID number if it is, or the empty string if not.
Class ObjectScript.Lookup2 { /// user makes a choice from the matches array, return the corresponding ID or "" ClassMethod Select(ByRef matches as %Integer, Output id as %Integer) { set id = "" for { read !!, "Choose by number: ", choice quit:(choice = "") set id = $get(matches(choice)) quit:(id '= "") // stop looping if user makes a valid choice write "...Invalid choice" } } }
-
Now you have to add a call to Select() from DOB(), Phone(), and Name(). In DOB(), add this at the end of the method.
do ..Select(.matches, .id)
In Phone() and Name(), update the "no matches" If at the end of the method to look like this.
if (count = 0) {write "...no matches"} else {do ..Select(.matches, .id)}
Modify the signatures of DOB(), Phone(), and Name() to return the chosen ID number.
Class ObjectScript.Lookup2 { ClassMethod DOB(intdob as %Date, output id as %Integer) { } ClassMethod Phone(phone as %String, output id as %Integer) { } ClassMethod Name(name as %String, output id as %Integer) { } }
To ensure that DOB(), Phone(), and Name() always return the chosen ID number, initialize id at the top of each method.
set id = ""
Modify Main() again, passing id by reference to DOB(), Phone(), and Name().
do ..CurrentCount() while ..GetInput(.type, .search) { if (type = "help") {do ..Help()} elseif (type = "phone") {do ..Phone(search, .id)} elseif (type = "name") {do ..Name(search, .id)} elseif (type = "dob") {do ..DOB(search, .id)} }
-
TakeAction() is very simple for now, just retrieving and displaying a record.
Class ObjectScript.Lookup2 { /// display chosen record ClassMethod TakeAction(id as %Integer) { set record = ^PersonD(id) do ##class(ObjectScript.DataEntry4).Display(record) } }
Modify Main() to call TakeAction() if the input is good. Also, since Help() doesn't return id like the other three methods, add set id = "" to the If.
do ..CurrentCount() while ..GetInput(.type, .search) { if (type = "help") {do ..Help() set id = ""} elseif (type = "phone") {do ..Phone(search, .id)} elseif (type = "name") {do ..Name(search, .id)} elseif (type = "dob") {do ..DOB(search, .id)} if ((type '= "") && (id '= "")) {do ..TakeAction(id)} }
-
Click the Save and Compile buttons.
-
Start the Terminal, and run your method to test the final changes, by typing do ##class(ObjectScript.Lookup2).Main().