LHC-Forms

A widget for rendering feature-rich forms defined in JSON definition files


What is LHC-Forms?

LHC-Forms (a.k.a. LForms) is a feature-rich, open-source widget that creates input forms based on definition files for Web-based applications. It was developed by the Lister Hill National Center for Biomedical Communications (LHNCBC), National Library of Medicine (NLM), part of the National Institutes of Health (NIH), with the collaboration and support from the Regenstrief Institute, Inc. and the LOINC Committee.

Features

See the demos for a step-by-step introduction. Features include:

Installation

LHC-Forms can be used either as a prebuilt package or installed using either the bower package manager (i.e. bower install lforms) or npm (npm install lforms). If you aren't using bower or npm, the simplest way to start using LHC-Forms is by linking to the prebuilt version hosted on clinicaltables.nlm.nih.gov. The URLs for the JavaScript and the CSS are:

JavaScript
https://clinicaltables.nlm.nih.gov/lforms-versions/17.0.0/lforms.min.js
CSS
https://clinicaltables.nlm.nih.gov/lforms-versions/17.0.0/styles/lforms.min.css

(Note that the URLs contain a release version number, 17.0.0, for which you can substitute any of the other prebuilt versions available at https://clinicaltables.nlm.nih.gov/lforms-versions).

If you decide to use the npm package, you will probably need to use webpack before it will be usable in a browser, so this option is likely only useful if you are already using webpack. (See the webpack.config.js file for how we build the bower and browser-ready versions, but if you are using webpack for your application, you should be able to just require the lforms package to pull it in.)

Usage

Although LHC-Forms internally uses AngularJS and offers a directive for use by AngularJS projects, you do not need to know anything about AngularJS to use it.

Rendering a Form (Without AngularJS)

In your HTML, create a an element to hold the form. You will pass a reference to this element or its ID to a utility function load the form.

HTML (without AngularJS)

<div id="myFormContainer"></div>

Also include the LHC-Forms JavaScript and CSS files on the page. In your page header:


<link
 href="https://clinicaltables.nlm.nih.gov/lforms-versions/17.0.0/styles/lforms.min.css"
 media="screen" rel="stylesheet" />
And with your other JavaScript:
<script
 src="https://clinicaltables.nlm.nih.gov/lforms-versions/17.0.0/lforms.min.js"
 ></script>

JavaScript (without AngularJS)

LForms.Util.addFormToPage(formDefinition, 'myFormContainer', options);
Parameters:
  1. formDefinition - A form definition (either JSON or a parsed object)
  2. formContainer - An element (or its ID, as in this example) into which the form should be placed
  3. options - A map of options. If you are using FHIR and want prepopulation to occur, pass {prepopulate: true}.

Rendering a Form (AngularJS Method)

As in the non-Angular method, you will need to include the LHC-Forms CSS and JavaScript (possibly using bower and wiredep, if you are using those).

HTML

The HTML in your page will look something like:

<body ng-app="myApp">
  <div ng-controller="myController">
    <lforms lf-data="myFormDefinition"></lforms>
  </div>
</body>

The directive is contained by a controller (in this example named "myController") which will have the responsibility of providing the form definition data as a JSON object (in this example named "myFormDefinition").

JavaScript

In the JavaScript for the AngularJS app, include 'lformsWidget' as a module to be loaded. Then, in the JavaScript for the AngularJS controller, construct an LFormsData object with the JSON form definition and assign that object to the scope variable "lfData". The form should initialize and display. For example:

angular.module('myApp', ['lformsWidget'])
  .controller('myController', ['$scope', function ($scope) {
    $scope.myFormData = new LForms.LFormsData(myFormDefinition);
  }]);

Retrieving User-Entered Data

After the user fills out a form, the data they have entered and things like codes for coded answer lists will be stored in the data model. To retrieve that data, LHC-Forms provides the following utility method:

LForms.Util.getUserData(formElement, noFormDefData, noEmptyValue, noHiddenItem)
Or, from wihin an AngularJS app, there is also an API on the LFormsData object:

$scope.myFormData.getUserData(noFormDefData, noEmptyValue, noHiddenItem)

With no arguments (i.e. LForms.Util.getUserData()), the data for the first LForm found on that page will be returned, and will include the form definition, along with entries for questions the user left blank and for questions that were hidden by skip-logic (which the user might not have seen). This default return behavior can be changed by the parameters, but in all cases the returned data will follow the structure of the form, in that answers will be nested inside containing sections.

The parameters are:

  1. formElement - an HTML element that includes the LForm's rendered form. If this is omitted, the "body" tag will be used. If there is more than one LForm within formElement, the first found will be used.
  2. noFormDefData (optional, default false) - If this is true, the form definition data will not be returned along with the data.
  3. noEmptyValue (optional, default false) - If this is true, items that have an empty value will be removed.
  4. noHiddenItem (optional, default false) - If this is true, items that are hidden by skip logic will be removed.

As an example, here is the data from a partially filled-in vital signs panel, returned via $scope.myFormData.getUserData(null, true, true, true):

    {
      "itemsData": [{
        "questionCode": "35094-2",
        "items": [{
          "questionCode": "8480-6",
          "value": "100",
          "unit": {
            "name": "mm[Hg]",
            "default": false,
            "normalRange": null,
            "absoluteRange": null
          }
        }, {
          "questionCode": "8357-6",
          "value": {
            "label": null,
            "code": "LA24014-5",
            "text": "Oscillometry",
            "other": null
          }
        }]
      }],
      "templateData": [{
        "value": "2015-11-09T05:00:00.000Z"
      }]
    }

The first section in the returned data "itemsData" contains all of the data from the form itself. LHC-Forms (optionally) adds a section to the top of the form that includes fields like "Date" and "Comment", and the data for these elements shows up in "templateData". The form definition data was not included in the above example, but you can see the structure if you look closely at itemsData. In this form, there was a section (question code "35094-2") which contained both of the two filled-in items as data. That is why there is just one entry in the itemsData itself, and that item has two sub-items in "items" array. One of the two items was numeric and had an associated unit field, while the other was a coded list field.

Another utility method, LForms.Util.getFormData(), will return both the user data and form definition data together in a way that can be fed back to LHC-Forms to display the form with the data. This is useful when you want to allow the user to save the form so it can be redisplayed later for completion or editing.

Retrieving User-Entered Data as HL7 v2 OBR and OBX segments (version >= 12.8)

The user entered data, along with some form definition data can be retrieved as HL7 v2 OBR and OBX segments. To retrieve that data, LHC-Forms provides the following utility method:

    LForms.Util.getFormHL7Data(element)

The parameter is:

  1. element - (optional if there is only one LHC-Forms widget on the page.) The containing HTML element that includes the LHC-Forms rendered form. It could be the DOM element or its id.

Form Validation (version >= 26.3)

Prior to submitting the form, the form can be checked to make sure required fields are filled in and that the values have the correct format, by using the following function:

LForms.Util.checkValidity(formElement)
  

An array of errors will be returned if the form is invalid. If the form is valid this function returns null.

The parameter is:

  1. formElement - (optional) an HTML element that includes the LForm's rendered form. If this is omitted, the "body" tag will be used. If there is more than one LForm within formElement, the first found will be used.

Support for FHIR Questionnaire Resources

LHC-Forms can import and export FHIR Questionnaire and QuestionnaireResponse resources. Also, there is partial support for importing and exporting DiagnosticReports (but those need to be generated by LHC-Forms).

Files for FHIR Support

Because adding support for FHIR (and multiple versions of FHIR) would increase the size of the LHC-Forms library, support for FHIR is added through separate files. If you are using one of the prebuilt versions, you can include (for FHIR version "R4"):

<script src="https://clinicaltables.nlm.nih.gov/lforms-versions/17.0.0/fhir/R4/lformsFHIR.min.js"></script>
For support for version "STU3", replace the "R4" in the URL with "STU3". It is safe to include both versions if your application needs both. Note that these files depend on the lforms.min.js file, so the script tag should follow that one. See the demo page for an example.

If you are installing via bower, the additional files to include are:

As with the prebuilt files, you may include either the R4 or the STU3 version, or both.

If you are installing via npm, the additional files to require are:

Exporting FHIR Resources

The form definiton and user-entered data can be retrieved as FHIR resources, using the following utility method:

    LForms.Util.getFormFHIRData(resourceType, fhirVersion, formDataSource, options)

The parameters are:

  1. resourceType - a FHIR resource type. Currently only "DiagnosticReport", "Questionnaire" (SDC profile) and "QuestionnaireResponse" (SDC profile) are supported as parameter values.
  2. fhirVersion - the version of FHIR being used. This can be set to either "STU3" or "R4".
  3. formDataSource - Optional. Either the containing HTML element that includes the rendered form, a CSS selector for that element, an LFormsData object, or an LHC-Forms form definition (parsed). If not provided, the first LHC-Forms form found in the page will be used.
  4. options - An optional hash of other options, with the following optional keys:
    • bundleType - optional, and only used for DiagnosticReport output. This may be either "transaction" or "collection", and requests that the DiagnosticReport and associated Observation resources be placed together in a bundle of that type. When this is not present, a bundle will not be used.
    • noExtensions - a flag that a standard FHIR Questionnaire or QuestionnaireResponse is to be created without any extensions, when resourceType is Questionnaire or QuestionnaireResponse. The default is false.
    • extract - a flag used for resourceType=QuestionnaireReponse to indicate that data should be extracted (using the observationExtract extension). In this case the returned resource will be an array consisting of the QuestionnaireResponse and any extracted Observations.
    • subject: A local FHIR resource that is the subject of the output resource. If provided, a reference to this resource will be added to the output FHIR resource when applicable.

Converting a FHIR Questionnaire Resource to the LHC-Forms Format

FHIR Questionnaire resource can be converted/imported into the LHC-Forms format, which can then be loaded into the LHC-Forms widget to show the form. To do that conversion, LHC-Forms provides the following utility method:

    LForms.Util.convertFHIRQuestionnaireToLForms(fhirData, fhirVersion)

The parameters are:

  1. fhirData - a FHIR Questionnaire resource (parsed). Note that our support for Questionnaire is incomplete but in progress. If you run into a problem, please open an issue on the LForms repository.
  2. fhirVersion - the version of FHIR in which the Questionnaire is written (currently takes either "STU3" or "R4"). This maybe be omitted if the Questionnaire resource (in fhirData) conains a meta.profile attibute specifying the FHIR version. If both are provided, this takes precedence.

The return value is a form definition in the LHC-Forms format.

FHIR Queries

If the Questionnaire referenced external FHIR resources, those will still need to be loaded. Before that can happen, you will need to tell LForms about the connection to the FHIR server, via:

LForms.Util.setFHIRContext(fhirContext)

The "fhirContext" parameter should be an instance of 'client-js', a.k.a. the npm package fhirclient, version 2. (See http://docs.smarthealthit.org/client-js for documentation.). A non-npm version, ready to use in a browser, can be found at https://cdn.jsdelivr.net/npm/fhirclient@2/build/fhir-client.min.js.

If rendering using LForms.Util.addFormtoPage(), the external FHIR resources will be loded when that is called, and if you have passed in the prepopulate=true as an option, prepoluation will also be performed when the resources are loaded. Note that you probably do not want prepopulation to happen if you are planning to merge in a saved QuestionnaireResponse. The addFormToPage function returns a promise that will resolve after all external FHIR resources have been loaded (at which point the form should be visible).

If using the AngularJS approach, then after the LFormsData instance is created, you will need to tell it to load the FHIR resources, passing in a flag to determine whether prepopulation should be done, like:


     // AngularJS example
     var myFormData = new LForms.LFormsData(myFormDefinition);
     myFormData.loadFHIRResources(true);
  

The "loadFHIRResources" function returns a Promise that will resolve when the referenced FHIR resources have finished loading, and the form is ready to display. This lets the application put up a spinner or some notice for the user, in case the loading takes a few seconds. For example:


     // AngularJS example with spinner
     $('.spinner').show();
     var lfData = new LForms.LFormsData(questionnaire);
     // Next call assumes LForms.Util.setFHIRContext(...) has been called.
     lfData.loadFHIRResources(true).then(function() {
       $('.spinner').hide();
       $scope.$apply(function() {
         // assign lfData to property of $scope
       });
     });
  

Merging FHIR QuestionnaireRepsonse into an LHC-Forms Form

FHIR QuestionnaireResponses (and also a DSTU2 version of DiagnosticReports, if generated by LHC-Forms) can be loaded into an LHC-Forms form definition to produce a structure that can be loaded into the LHC-Forms widget (using the same methods as for JSON form defintions described above) to show the form along with the user data. To merge FHIR resources into LHC-Forms data, LHC-Forms provides the following utility method:

    LForms.Util.mergeFHIRDataIntoLForms(resourceType, fhirData, formData, fhirVersion)

The parameters are:

  1. resourceType - a FHIR resource type. Currently only "QuestionnaireResponse" (SDC profile) and "DiagnosticReport" (either a DiagnosticReport resource with "contained" Observation resources or a "searchset" typed Bundle of a DiagnosticReport and associated Observation resources) are supported.
  2. fhirData - a FHIR resource of the type specified in the resourceType parameter. Note that for DiagnosticReports, the merge will only work if the DiagnosticReport was originally generated by LHC-Forms.
  3. formData - an LHC-Forms form definition or data object.
  4. fhirVersion - the version of FHIR in which the resource is written (currently takes either "STU3" or "R4"). This maybe be omitted if the resource (in fhirData) conains a meta.profile attibute specifying the FHIR version. If both are provided, this takes precedence.

The return value is a form definition object but with the user data merged into it. It can be displayed using LForms.Util.addFormToPage described above.

Form Definition Format

Form definitions are stored in a JSON structure. To get a rough idea of what these are you can take a look at one of the samples, or for a detailed description see the form definition documentation.

Licensing and Copyright Notice

See the LICENSE.md file in the lforms package on GitHub.


Project maintained by lhncbc Hosted on GitHub Pages — Based on theme by mattgraham