We begin with a root HTML that sets up my Single Page Application, and a reference to my Rhizome library, which contains a number of useful client-side services:
<body id="content" style="display: none;" ng-app="questioncare">
<h3 ng-controller="ErrorController" ng-bind="errorText" ng-show="showError"></h3>
<div id="request" ng-controller="RequestController">
<button id="view-questionnaire" ng-click= "viewQuestionnaire()">View Questionnaire</button>
</div>
<ng-include src="res/templates/Questionnaire.html"> </ng-include>
This corresponds to a single line in the javascript initialization:
var questioncare = angular.module('questioncare', ['rhizome', 'ngSanitize']);We'll see how the ngSanitize module is required in order to handle rendering the human-readable HTML div from the Questionnaire resource as HTML, instead of text, using ng-bind-html. In any case, we are setting up a controller to handle a request, and then we are including a template to handle the response. The rest, as we shall see is handled through controller code and client-side services, but let's take a quick look at the included template for Questionnaire:
<div id="questionnaireResponse" ng-controller="QuestionnaireController" ng-show="showQuestionnaire">That takes care of the HTML. The request controller invokes an adapter (this is running on IBM Worklight), and then sends the response to the questionnaire controller using $rootScope.broadcast, but first it calls a client-side service which I have made responsible for managing codes; in this case, the value sets for the different options you can pick when you answer the questionnaire.
<div ng-bind-html="humanReadable"></div> <hr/>
<div ng-bind="questionnaire.name.text"></div>
<div ng-bind="group.header"></div>
<hr/>
<ol id="questions">
<li ng-repeat="question in group.question">
<div ng-bind="question.text"></div>
<ol id="options">
<li ng-repeat="option in getOptions(question.options.reference)"> [<span ng-bind="option.code"></span>]:
<span ng-bind="option.display"></span>
</li>
</ol>
</li>
</ol>
</div>
questioncare.controller( 'RequestController',
function($scope, $http, $rootScope, errorService, codeService) {
$scope.viewQuestionnaire = function() {
var invocationData = {
adapter: 'FHIR',
procedure: 'getQuestionnaire',
parameters: []
};
WL.Client.invokeProcedure(invocationData, {
onSuccess : function(result) {
if (200 == result.status) {
var ir = result.invocationResult;
if (true == ir.isSuccessful) {
$scope.$apply(function () {
var questRes = ir.content;
codeService.loadCodedConcepts(questRes.contained); $rootScope.$broadcast('qr', questRes);
});
} else {
errorService.worklightError('Bad Request');
};
} else {
errorService.worklightError('Http Failure ' + result.status);
};
},
onFailure : errorService.worklightError
});
}
});
The codeService itself is quite simple, although, since value sets could potentially come from a variety of places, this service could become a lot more complicated. In this case, I am just scraping contained value sets from the Questionnaire resource itself:
rhizome.factory('codeService', function($rootScope, errorService) {Angular services can be difficult to grasp at first, but they are one of the more important features of the framework, since they allow you to make your client-side more portable and standardized; however, this particular service is little more than a stub at this point. It deals with a hash sign which is probably included with the value set id, which is useful. Once the coded concepts have been scraped out of the Questionnaire, the document is displayed using the included template and a response controller.
var codeService = {};
codeService.codedConcept = Object;
codeService.loadCodedConcepts = function(contained) {
for (c in contained) {
codeService.codedConcept[contained[c].id] = contained[c].define.concept;
}
};
codeService.getCodedConcept = function(opt, remHash) {
if (remHash) {
opt = opt.substr(1);
}
return(codeService.codedConcept[opt]);
};
return codeService;
});
questioncare.controller( 'QuestionnaireController',Again, there is nothing too complicated here. Notice how the humanReadable questionnaire text div gets bound into an element that allows HTML to be rendered. Also, a second function is used to get and then display the options because these need to be repeated, as you can see in the Questionnaire.html. In addition, the entire questionnaire template is hidden until it is populated.
function($scope, errorService, codeService) {
$scope.showQuestionnaire = false;
$scope.$on('qr', function (event, arg) {
$scope.questionnaire = arg;
$scope.group = $scope.questionnaire.group;
$scope.humanReadable = $scope.questionnaire.text.div;
$scope.showQuestionnaire = true;
});
$scope.getOptions = function(opt) {
return codeService.getCodedConcept(opt, true);
};
});
Next steps here will be to work with nested questionnaires, where selected options will traverse through a hierarchy of question groups. At this point, it may be useful to use Angular custom directives, although I am also trying to be careful about anything that will be subject to change with Angular 2.0, such as controllers.
More and more as I work with Angular, Worklight and HL7 FHIR, it strikes me that what is important here is building a library of standard services and templates on the client side, and then simply binding into it. Once DSTU2 is complete for FHIR it will become less of a moving target, but resources like Questionnaire, which has been the subject of several connect-a-thons now, seem especially stable.
No comments:
Post a Comment