Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Added step 10, on additional features

...

Code Block
titleFurther validation in delete action
	/**
	 * Fragment Action for deleting an existing identifier
	 */
	public FragmentActionResult deleteIdentifier(UiUtils ui, @RequestParam("patientIdentifierId") Integer id,
	                                             @RequestParam(value="reason", defaultValue="user interface") String reason) {
		PatientService ps = Context.getPatientService();
		PatientIdentifier pid = ps.getPatientIdentifier(id);
		// don't touch it if it's already deleted
		if (pid.isVoided())
			return new FailureResult(ui.message("PatientIdentifier.delete.error.already"));
		// don't delete the last active identifier
		if (pid.getPatient().getActiveIdentifiers().size() == 1) {
			return new FailureResult(ui.message("PatientIdentifier.delete.error.last"));
		}
		// otherwise, we go ahead and delete it
		ps.voidPatientIdentifier(pid, reason);
		return FragmentUtil.standardPatientObject(ui, pid.getPatient());
	}

We also need to make one small change to the view in order to actually display those errors. Before we were flashing a generic message, that wasn't actually helpful to the user. Instead, we just pass the jQuery xml http response (it's the first argument that jQuery passes to the failure function for any ajax call) to a utility javascript method that the framework provides:

Code Block
titlechanging the view to support error messages raised by the delete action via ajax

    subscribe('${ id }.delete-button-clicked', function(message, data) {
        if (openmrsConfirm('${ ui.message("general.confirm") }')) {
            jq.post('${ ui.actionLink("deleteIdentifier") }', { returnFormat: 'json', patientIdentifierId: data },
            function(data) {
                flashSuccess('${ ui.escapeJs(ui.message("PatientIdentifier.deleted")) }');
                publish('patient/${ patient.patientId }/identifiers.changed', data);
            }, 'json')
            .error(function(xhr) {
                fragmentActionError(xhr, "Programmer error: delete identifier failed");
            })
        }
    });

Step 10: Additional features

Up until this point, we've covered the very basic functionality that pretty much every patient-based list of items will have. Namely, you can display the list, and add and remove items. Now let's add another feature that's a bit custom for this particular fragment: we should visually identify which one of the identifiers is the preferred one (it should already be at the top of the list) and allow the user to mark any non-preferred identifier as the newly-preferred one.

Of course, we need to write a fragment action to support setting a non-preferred identifier as preferred:

Code Block
titleFragment Action for changing the preferred identifier

	/**
	 * Fragment Action for marking an identifier as preferred
	 */
	public FragmentActionResult setPreferredIdentifier(UiUtils ui,
	                                                   @RequestParam("patientIdentifierId") PatientIdentifier pid) {
		PatientService ps = Context.getPatientService();
		if (pid.isVoided())
			return new FailureResult(ui.message("PatientIdentifier.setPreferred.error.deleted"));
		// silently do nothing if it's already preferred
		if (!pid.isPreferred()) {
			// mark all others as nonpreferred
			for (PatientIdentifier activePid : pid.getPatient().getActiveIdentifiers()) {
				if (!pid.equals(activePid) && activePid.isPreferred()) {
					activePid.setPreferred(false);
					ps.savePatientIdentifier(activePid);
				}
			}
			// mark this one as preferred
			pid.setPreferred(true);
			ps.savePatientIdentifier(pid);
		}
		return FragmentUtil.standardPatientObject(ui, pid.getPatient());
	}

This code is straightforward. The only difference between this example and those in previous steps is that we're converting the patientIdentifierId parameter directly into a PatientIdentifier directly in the method signature, using Spring's automatic type conversion. (Doing this required adding a converter class. TODO link to documentation of this.)

The next step is to add a column to the table in the gsp page, which shows either a preferred, or non-preferred icon. The non-preferred icon should be clickable.

Code Block
titleadditions to the view to support changing the preferred patient identifier

...
    subscribe('${ id }.set-preferred-identifier', function(message, data) {
        jq.post('${ ui.actionLink("setPreferredIdentifier") }', { returnFormat: 'json', patientIdentifierId: data },
        function(data) {
            flashSuccess('${ ui.escapeJs(ui.message("PatientIdentifier.setPreferred")) }');
            publish('patient/${ patient.patientId }/identifiers.changed', data);
        }, 'json')
        .error(function(xhr) {
            fragmentActionError(xhr, "Failed to set preferred identifier");
        })
    });
...
    ${ ui.includeFragment("widgets/table", [
            id: id + "_table",
            columns: [
                [ property: "identifierType", heading: ui.message("PatientIdentifier.identifierType") ],
                [ property: "identifier", userEntered: true, heading: ui.message("PatientIdentifier.identifier") ],
                [ property: "location", heading: ui.message("PatientIdentifier.location") ],
                [ heading: ui.message("PatientIdentifier.preferred"),
                    actions: [
                        [ action: "none",
                            icon: "star_16.png",
                            showIfPropertyTrue: "preferred" ],
                        [ action: "event",
                            icon: "star_off16.png",
                            event: id + ".set-preferred-identifier",
                            property: "patientIdentifierId",
                            showIfPropertyFalse: "preferred" ]
                    ]
                ],
                [ actions: [
                    [ action: "event",
                        icon: "delete24.png",
                        tooltip: ui.message("PatientIdentifier.delete"),
                        event: id + ".delete-button-clicked",
                        property: "patientIdentifierId" ]
                ] ]
            ],
            rows: patient.activeIdentifiers,
            ifNoRowsMessage: ui.message("general.none")
        ]) }
...

We add a callback function that will set a preferred identifier by doing a jQuery ajax post, which will be activated based on a message. Then we add a new column definition to the table, that contains two actions. These actions are different from what we've seen before because they are marked as being conditional by the attributes "showIfPropertyTrue" and "showIfPropertyFalse".

(Actually I just implemented this conditional display in TRUNK-2186. Generally speaking, if you're writing a fragment that needs functionality that will likely be needed elsewhere as well, you should be thinking about how to build this functionality in a shared way.)

So if we refresh our browser at this point, we'll see a new column, with a star that is either highlighted or greyed out, depending on whether the identifier is preferred.

When I actually clicked around on these I noticed some odd behavior (now fixed in the head revision of the project): marking an identifier as preferred doesn't immediately move it to the top of the list on the ajax refresh, but it does move to the top of the list the next time the page is loaded. Basically, there's a bug in the core OpenMRS API where changing the preferred identifier is not reflected in the patient.getaActiveIdentifiers() method until the next time the patient is loaded from the database. I reported this as TRUNK-2188, and I introduced a workaround in the SimplePatient object used indirectly by FragmentUtil.standardPatientObject().