Adding Prompts - Part 2
-
You may have noticed that our prompts don't actually do anything. There is nothing setup to trigger running our appointments table with the new prompts. Let's correct this by adding the necessary code.
Start by adding a new button to the prompts. I placed my button right after the "Sch State" prompt and before the "space between" span. This gives the user a clear indication that the button is there to execute any changes in prompt values.
We are also finally going to make use of the loading variable we added to the appointment data service. If loading is set to true, we want to disable the run button.
The code is as follows:
<!-- Run Button --> <button mat-icon-button title="Run" (click)="appointmentDS.loadAppointments()" [disabled]="appointmentDS.loading"> <mat-icon>play_arrow</mat-icon> </button>
If you were to click the run button on your MPage, you would see the button enter a disabled state as the CCL script runs.The play_arrow and save icons are part of the Material Icons library found in Google Fonts. You can see a complete list of icons available at https://fonts.google.com/icons?icon.set=Material+Icons&icon.query=play .
-
Our run button now works and executes our CCL script however if you look at the debugger you will see that none of our parameter values are being passed to CCL. To remedy this we need to add a parameters variable to our custom script execution statement.
Open appointment-data.service.ts and add a parameters variable that points directly to our prompts object. Your customService.load statement should appear as follows:
this.customService.load({ customScript: { script: [ { name: '1trn_appt_mp:group1', run: 'pre', id: 'appointments', parameters: this.prompts } ] } }, undefined, (() => { this.loading = false }));
-
After saving your change and when your page refreshes, open the activity log. You should see a fair bit of
activity between our CCL script run and the prompts.
The first thing you may notice is a "No open process instances available" message appearing multiple times. This indicates that our two queues assigned when starting our MPage have been filled and are waiting to clear before allowing another request to be made. For this MPage these messages can be ignored as it is happening due to our prompts performing initial lookups. If your MPage were larger or if performance was being affected you could either choose to add more queues to your setMaxInstances method in app.component.ts or perform bulk loading of code values and other data in single requests.
In our case, everything is running immediately so there is no noticeable delay for the end user.
I've highlighted the initial run where we can see that our prompt values are now being passed to our CCL script. In a moment we are going to modify our CCL script to make use of these new prompt values.
-
In your browser, make some prompt selections that you would expect to return values. Make sure you choose
"All Values" for at least one of the prompts to see that it stores [-1] as a value. Click the run button.
In the activity log, find the payload for your run near the top of the log and copy the entire payload JSON data into your clipboard. The content I'll be working with is shown below. Please take note that I did not copy the time, initiated message or Payload -> text.
{"payload":{"patientSource":[{"personId":0,"encntrId":0}],"customScript":{"script":[{"name":"1trn_appt_mp:group1","run":"pre","id":"appointments","parameters":{"dateType":"120","fromDate":"2023-02-11T16:20:17.995Z","toDate":"2023-02-11T16:20:17.995Z","appointmentType":[-1],"resource":[-1],"location":[],"schState":[4536,4537,4538]}}]}}}
-
Launch Discern Visual Developer and open both your 1trn_mp_appointments.prg and 1trn_appt_mp.prg scripts. If you recall, 1trn_mp_appointments.prg is your test script.
Replace the payload information inside your request->BLOB_IN statement with the content in your clipboard. This content will be wider than the CCL 132-character limit so you will need to break the string into multiple strings as I have done in the code below.
set request->BLOB_IN = concat(^{"payload":{"patientSource":[{"personId":0,"encntrId":0}],^, ^"customScript":{"script":[{"name":"1trn_appt_mp:group1","run":"pre","id":"appointments",^, ^"parameters":{"dateType":"120","fromDate":"2023-02-11T16:20:17.995Z",^, ^"toDate":"2023-02-11T16:20:17.995Z","appointmentType":[-1],"resource":[-1],^, ^"location":[],"schState":[4536,4537,4538]}}]}}}^)
Save/Compile your code and ensure it runs with the back-end statement you used previously.
1trn_mp_appointment:group1 "MINE" go
-
Switch over to 1trn_appt_mp.prg and add the following line of code right before your
rCustom declaration.
; Show incoming parameters call echorecord(payload->customScript->script[nScript])
Go ahead and run your test script again from the back-end. You should see your parameters in the output.If you have done much in the way of CCL work you will notice an immediate problem with my output above. All of our numeric prompt values (e.g. schstate) are being stored as integer numbers and not floating point numbers.
This is due to the way the Cerner CNVTJSONTOREC() function works in that it tests the first value of an object to determine its type. In my example, code value 4536 is small enough that CNVTJSONTOREC() thinks it is an integer.
Often this isn't going to cause a problem unless a value in your prompts is too large to fit in an integer. For example, if that last code value in the list was 4004266123, it would appear as -2147483648 in our record structure and fail on any lookups.
The simple solution to fixing this problem is to force our parameters into an existing record structure where we can define each data type. To do this we need to go back to our Angular application and change our parameters to a string. Do this by using the JSON.stringify method in appointment-data.service.ts as shown below.
this.customService.load({ customScript: { script: [ { name: '1trn_appt_mp:group1', run: 'pre', id: 'appointments', parameters: JSON.stringify(this.prompts) } ] } }, undefined, (() => { this.loading = false }));
-
After adding the JSON.stringify method to your code, select your prompts again, copy the payload and replace
the request->BLOB_IN code in your 1trn_mp_appointment.prg script with your new code.
set request->BLOB_IN = concat(^{"payload":{"patientSource":[{"personId":0,"encntrId":0}],^, ^"customScript":{"script":[{"name":"1trn_appt_mp:group1","run":"pre","id":"appointments",^, ^"parameters":"{\"dateType\":\"120\",\"fromDate\":\"2023-02-11T17:39:31.895Z\",\"toDate\":^, ^\"2023-02-11T17:39:31.895Z\",\"appointmentType\":[-1],\"resource\":[-1],\"location\":[],^, ^\"schState\":[4536,4537,4538]}"}]}}}^)
- Run your test script again. You should now see the parameters variable show as a single string.
-
Switch back to 1trn_appt_mp.prg and add the following code between your echorecord
call and the free record rCustom statement.
free record rParam record rParam ( 1 dateType = vc 1 fromDate = dq8 1 toDate = dq8 1 appointmentType[*] = f8 1 resource[*] = f8 1 location[*] = f8 1 schState[*] = f8 ) set stat = cnvtjsontorec(build(^{"rParam":^,payload->customScript->script[nScript].parameters, ^}^), 0, 0, 1) call echorecord(rParam)
The code above defines a new record structure called rParam which has all of our prompt values defined as the correct CCL data types. When the CNVTJSONTOREC statement is executed, we add "rParam" as the record structure name and CCL automatically knows to use the current definition.
The final echorecord statement shows the output of our "rParam" record structure.
-
There are many ways we can use the rParam structure to collect and filter our data. My preference is to make
use of CCL PARSER statements. After the record definition of rCustom and before you run the select statement,
add the following code to declare our parsers.
; Declare parser statements declare cDateParser = vc with noconstant("1=1") declare cApptTypeParser = vc with noconstant("1=1") declare cResourceParser = vc with noconstant("1=1") declare cLocationParser = vc with noconstant("1=1") declare cStateParser = vc with noconstant("1=1")
-
Navigate to your select statement and add the parser statements where shown in the
code sample below. You will also need the expand control option so add it to your with statement as well.
; Collect appointments select into "nl:" from sch_appt sa, sch_event se, sch_appt sa2 plan sa where sa.person_id = chart_id->person_id and parser(cDateParser) and parser(cLocationParser) and parser(cStateParser) and sa.role_meaning = "PATIENT" and sa.state_meaning in ("CONFIRMED", "CHECKED IN", "CHECKED OUT") and sa.version_dt_tm > sysdate and sa.active_ind = 1 and sa.end_effective_dt_tm > sysdate join se where se.sch_event_id = sa.sch_event_id and parser(cApptTypeParser) and se.version_dt_tm > sysdate and se.active_ind = 1 and se.end_effective_dt_tm > sysdate join sa2 where sa2.sch_event_id = se.sch_event_id and parser(cResourceParser) and sa2.role_meaning = "RESOURCE" and sa2.state_meaning in ("CONFIRMED", "CHECKED IN", "CHECKED OUT") and sa2.version_dt_tm > sysdate and sa2.active_ind = 1 and sa2.end_effective_dt_tm > sysdate order sa.beg_dt_tm head report nNum = 0 detail nNum = nNum + 1 stat = alterlist(rCustom->appointments, nNum) rCustom->appointments[nNum].sch_event_id = sa.sch_event_id rCustom->appointments[nNum].encntr_id = sa.encntr_id rCustom->appointments[nNum].begin_dt_tm = sa.beg_dt_tm rCustom->appointments[nNum].duration = sa.duration rCustom->appointments[nNum].appt_type = uar_get_code_display(se.appt_type_cd) rCustom->appointments[nNum].resource = uar_get_code_display(sa2.resource_cd) rCustom->appointments[nNum].location = uar_get_code_display(sa.appt_location_cd) rCustom->appointments[nNum].sch_state = uar_get_code_display(sa.sch_state_cd) with expand=1, counter
- Compile and test your CCL script. You should not see any errors or difference in output as all of your parsers have the value of 1=1.
-
The remaining code we will be adding to the CCL script will be between the "declare cStateParser" line and your select statement.
Start with defining the date parser. For values of "ALL" we won't need to take any action as the 1=1 value in cDateParser will meet that qualification. For date range values we will need to have a string that looks between two or two date values. Our 30, 60 and 120 ranges will need to calculate dates between now and 30, 60 or 120 days.
The code for this is as follows:
; Build Date parser if (rParam->dateType = "DATE") ; Date Range set cDateParser = concat(^sa.beg_dt_tm between cnvtdatetime("^, format(rParam->fromDate, "dd-mmm-yyyy;;d"), ^") and cnvtdatetime("^, format(rParam->toDate, "dd-mmm-yyyy;;d"), ^ 23:59:59")^) elseif (rParam->dateType != "ALL") ; 30, 60, 120 days set cDateParser = concat(^sa.beg_dt_tm between sysdate and cnvtlookahead("^, rParam->dateType, ^D")^) endif
-
The appointment type, resource and state parsers will all make use of arrays that can have on of three different states. The array can either be empty (indicating the user did not make a choice), have single entry with a value of -1 or be filled with one or more code values.
The best way to handle each entry is with an if statement that first checks the size of the array followed by a check for the value of -1 in the first element.
Enter the following code which will accommodate all three parsers.
declare nNum = i4 ; Needed by the expand statement ; Appointment Type Parser if (size(rParam->appointmentType, 5) > 0 and rParam->appointmentType[1] != -1) set cApptTypeParser = ^expand(nNum, 1, size(rParam->appointmentType, 5), se.appt_type_cd, cnvtreal(rParam->appointmentType[nNum]))^ endif ; Resource Parser if (size(rParam->resource, 5) > 0 and rParam->resource[1] != -1) set cResourceParser = ^expand(nNum, 1, size(rParam->resource, 5), sa2.resource_cd, cnvtreal(rParam->resource[nNum]))^ endif ; State Parser if (size(rParam->schState, 5) > 0 and rParam->schState[1] != -1) set cStateParser = ^expand(nNum, 1, size(rParam->schState, 5), sa.sch_state_cd, cnvtreal(rParam->schState[nNum]))^ endif
-
The final step is to build the parser for the location. This involves executing the 1co_location_routines CCL script which requires the same value we used in our scriptParams property on the mpage-tree component in our HTML as well as the name of the record structure element that contains our location choices.
Once run, the script will generate a record structure called rFilteredLocations which contains the data we need for our parser.
The code is as follows:
; Location Parser if (size(rParam->location, 5) > 0) execute 1co_location_routines:group1 ^"maxViewLevel":"UNIT"^, ^rParam->location^ set cLocationParser = ^expand(nNum, 1, size(rFilteredLocations->data, 5), sa.appt_location_cd, rFilteredLocations->data[nNum].location_cd)^ endif
- Save and compile your CCL code. You can test your prompts in your MPage if you wish.