Skip to content

Commit

Permalink
feat(components): add ui-grid loading indicator (#445)
Browse files Browse the repository at this point in the history
This commit adds a `bhGridLoadingIndicator`, a component that shows nice
messages on loading states, empty states, and error states.  The basic
usage looks like this:
```html
<div ui-grid>
  <bh-grid-loading-indicator
    loading-state="Ctrl.loading"
    empty-state="Ctrl.grid.data.length === 0"
    error-state="Ctrl.hasError">
  </bh-grid-loading-indicator>
</div>
```

The `hasError` state takes precedence over all other states - if this
value is `true`, it will not show any of the other loading indicators,
even if data has loaded or is loading.  The message is very generic -
one is expected to report the proper error using `Notify.danger()` or
`Notify.handleError()`.

For a real example, see the billing services module and patient
registry.
  • Loading branch information
jniles authored and sfount committed May 27, 2016
1 parent 655f044 commit b944ef6
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 84 deletions.
3 changes: 2 additions & 1 deletion client/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@
"ERRORS": {
"ERR_INTERNET_DISCONNECTED" : "You are not connected to the network.",
"NO_EXCHANGE_RATE" : "No Exchange rate defined",
"UNAUTHORIZED" : "You have submitted a bad username and password combination. Please login in with valid credentials to continue."
"UNAUTHORIZED" : "You have submitted a bad username and password combination. Please login in with valid credentials to continue.",
"UNKNOWN" : "An error occurred."
},
"EXCHANGE": {
"ADDING_RATE" : "Adding an exchange Rate",
Expand Down
9 changes: 9 additions & 0 deletions client/src/js/components/bhGridLoadingIndicator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
angular.module('bhima.components')
.component('bhGridLoadingIndicator', {
bindings: {
loadingState: '<',
emptyState: '<',
errorState: '<'
},
templateUrl : 'partials/templates/bhGridLoadingIndicator.tmpl.html',
});
30 changes: 6 additions & 24 deletions client/src/partials/billing_services/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,31 +33,13 @@
ui-grid-cellNav
ui-grid-auto-resize>

<!-- indicate that the grid is loading -->
<div class="grid-msg-overlay" ng-show="BillingServicesCtrl.loading">
<div class="msg">
<span><span class="glyphicon glyphicon-refresh"></span> {{ "TABLE.COLUMNS.LOADING" | translate }}</span>
</div>
</div>

<!-- indicate that there is no data for the requested query -->
<div class="grid-msg-overlay" ng-hide="BillingServicesCtrl.loading || BillingServicesCtrl.options.data.length > 0">
<div class="msg">
<span class="text-info">
<span class="glyphicon glyphicon-info-sign"></span> {{ "TABLE.COLUMNS.EMPTY" | translate }}
</span>
</div>
</div>
<bh-grid-loading-indicator
loading-state="BillingServicesCtrl.loading"
empty-state="BillingServicesCtrl.options.data.length === 0"
error-state="BillingServicesCtrl.hasError"
>
</bh-grid-loading-indicator>

<!-- indicate that some error occurred -->
<div class="grid-msg-overlay" ng-show="BillingServicesCtrl.error">
<div class="msg">
<!-- @todo - make an error directive for this kind of text -->
<span class="text-danger">
<span class="glyphicon glyphicon-alert"></span> {{ BillingServicesCtrl.error.code | translate }}
</span>
</div>
</div>
</div>
</div>
</div>
10 changes: 5 additions & 5 deletions client/src/partials/patients/registry/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,20 @@ function PatientRegistryModalController( $uibModalInstance, Inventory, patients,

var patient = angular.copy(vm.patient);
patient.detail = 1;

var promise = patients.search(patient);
var patientFilters = patients.patientFilters(patient);

promise
.then(function (response) {
.then(function (response) {
var data = {
response : response,
filters : patientFilters
filters : patientFilters
};

return $uibModalInstance.close(data);

});
});
}

function setTimes(times){
Expand Down
39 changes: 16 additions & 23 deletions client/src/partials/patients/registry/registry.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,28 @@

<!-- @todo remove hardcoded styles -->
<div class="flex-util" style="min-height : 35px; padding-top : 7px">
<bh-filters-applied
<bh-filters-applied
filters="PatientRegistryCtrl.filters">
</bh-filters-applied>
</div>

<div class="flex-content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div id="patient-registry" ui-grid="PatientRegistryCtrl.uiGridOptions" style="height : calc(100vh - 102px)" ui-grid-auto-resize ui-grid-resize-columns ui-grid-move-columns>
<div
id="patient-registry"
ui-grid="PatientRegistryCtrl.uiGridOptions"
style="height : calc(100vh - 102px)"
ui-grid-auto-resize
ui-grid-resize-columns
ui-grid-move-columns>

<!-- @todo these states can probably be a component that accepts loading and data -->
<!-- No Records State -->
<div class="grid-msg-overlay" ng-hide="PatientRegistryCtrl.loading || PatientRegistryCtrl.uiGridOptions.data.length > 0">
<div class="msg">
<span class="text-info">
<span class="glyphicon glyphicon-info-sign"></span> {{ "TABLE.COLUMNS.EMPTY" | translate }}
</span>
</div>
</div>
<bh-grid-loading-indicator
loading-state="PatientRegistryCtrl.loading"
empty-state="PatientRegistryCtrl.uiGridOptions.data.length === 0"
error-state="PatientRegistryCtrl.hasError"
>
</bh-grid-loading-indicator>

<!-- Loading State -->
<div class="grid-msg-overlay" ng-show="PatientRegistryCtrl.loading">
<div class="msg">
<span><span class="glyphicon glyphicon-refresh"></span> {{ "TABLE.COLUMNS.LOADING" | translate }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
58 changes: 33 additions & 25 deletions client/src/partials/patients/registry/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ angular.module('bhima.controllers')
.controller('PatientRegistryController', PatientRegistryController);

PatientRegistryController.$inject = [
'$translate', 'PatientService', '$uibModal', 'NotifyService'
'PatientService', '$uibModal', 'NotifyService', 'moment'
];

/**
Expand All @@ -12,73 +12,81 @@ PatientRegistryController.$inject = [
* of Patient Registry.
*
*/
function PatientRegistryController($translate, Patients, $uibModal, Notify) {
function PatientRegistryController(Patients, $uibModal, Notify, moment) {
var vm = this;

var patientActionsTemplate = '<div style="padding : 5px"><a ui-sref="patientRecord({patientID : row.entity.uuid})"><span class="glyphicon glyphicon-list-alt"></span> {{ "PATIENT_REGISTRY.RECORD" | translate }}</a> <a ui-sref="patientEdit({uuid : row.entity.uuid})"><span class="glyphicon glyphicon-edit"></span> {{ "TABLE.COLUMNS.EDIT" | translate }}</a></div>'


var patientActionsTemplate =
'<div style="padding : 5px"><a ui-sref="patientRecord({patientID : row.entity.uuid})"><span class="glyphicon glyphicon-list-alt"></span> {{ "PATIENT_REGISTRY.RECORD" | translate }}</a> <a ui-sref="patientEdit({uuid : row.entity.uuid})"><span class="glyphicon glyphicon-edit"></span> {{ "TABLE.COLUMNS.EDIT" | translate }}</a></div>';

vm.search = search;
vm.momentAge = momentAge;

// track if module is making a HTTP request for patients
vm.loading = false;
/** TODO MANAGE COLUMN : LAST_TRANSACTION */

/** TODO manage column : last_transaction */
// the column attribute `displayName` must be used in favour of `name` in order to allow `headerCellFilter` to function
vm.uiGridOptions = {
appScopeProvider : vm,
enableColumnMenus : false,
headerCellFilter: 'translate',
columnDefs : [
{ field : 'reference', displayName : 'TABLE.COLUMNS.REFERENCE', headerCellFilter: 'translate' },
{ field : 'patientName', displayName : 'TABLE.COLUMNS.NAME', headerCellFilter : 'translate' },
{ field : 'patientAge', displayName : 'TABLE.COLUMNS.AGE', headerCellFilter : 'translate' },
{ field : 'patientAge', displayName : 'TABLE.COLUMNS.AGE', headerCellFilter : 'translate' },
{ field : 'sex', displayName : 'TABLE.COLUMNS.GENDER', headerCellFilter : 'translate' },
{ field : 'hospital_no', displayName : 'TABLE.COLUMNS.HOSPITAL_FILE_NR', headerCellFilter : 'translate' },
{ field : 'registration_date', cellFilter:'date', displayName : 'TABLE.COLUMNS.DATE_REGISTERED', headerCellFilter : 'translate' },
{ field : 'last_visit', cellFilter:'date', displayName : 'TABLE.COLUMNS.LAST_VISIT', headerCellFilter : 'translate' },
{ field : 'dob', cellFilter:'date', displayName : 'TABLE.COLUMNS.DOB', headerCellFilter : 'translate' },
{ name : 'Actions', displayName : '', cellTemplate : patientActionsTemplate}
{ field : 'registration_date', cellFilter:'date', displayName : 'TABLE.COLUMNS.DATE_REGISTERED', headerCellFilter : 'translate' },
{ field : 'last_visit', cellFilter:'date', displayName : 'TABLE.COLUMNS.LAST_VISIT', headerCellFilter : 'translate' },
{ field : 'dob', cellFilter:'date', displayName : 'TABLE.COLUMNS.DOB', headerCellFilter : 'translate' },
{ name : 'Actions', displayName : '', cellTemplate : patientActionsTemplate }
],
enableSorting : true
};


function handler(error) {
vm.hasError = true;
Notify.handleError(error);
}

// load Patient Registry Grid
function loadGrid() {
vm.loading = true;
vm.hasError = false;

Patients.read()
.then(function (patients) {
patients.forEach(function (patient) {
patient.patientAge = momentAge(patient.dob);
});
patient.patientAge = momentAge(patient.dob);
});
vm.uiGridOptions.data = patients;
})
.catch(Notify.handleError)
.finally(function () {
vm.loading = false;
.catch(handler)
.finally(function () {
vm.loading = false;
});
}

// Search and filter data in Patiens Registry
function search() {
vm.loading = true;
vm.loading = true;
vm.hasError = false;
Patients.openSearchModal()
.then(function (data) {
var response = data.response;
vm.filters = data.filters;
response.forEach(function (patient) {
patient.patientAge = momentAge(patient.dob);
});
patient.patientAge = momentAge(patient.dob);
});
vm.uiGridOptions.data = response;
})
.catch(Notify.handleError)
.catch(handler)
.finally(function () {
vm.loading = false;
});
}

// moment() provides the current date, similar to the new Date() API. This requests the difference between two dates
function momentAge(dateOfBirth){
// moment() provides the current date, similar to the new Date() API. This requests the difference between two dates
return moment().diff(dateOfBirth, 'years');
}

Expand Down
23 changes: 23 additions & 0 deletions client/src/partials/templates/bhGridLoadingIndicator.tmpl.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<div class="grid-msg-overlay" ng-show="!$ctrl.errorState && ($ctrl.loadingState || $ctrl.emptyState)">

<!-- indicate that the grid is loading -->
<div class="msg" ng-show="$ctrl.loadingState">
<span><span class="fa fa-spinner fa-spin"></span> {{ "TABLE.COLUMNS.LOADING" | translate }}</span>
</div>

<!-- indicate that there is no data for the requested query -->
<div class="msg" ng-show="!$ctrl.loadingState && $ctrl.emptyState">
<span class="text-info">
<span class="fa fa-info-circle"></span> {{ "TABLE.COLUMNS.EMPTY" | translate }}
</span>
</div>
</div>

<div class="grid-msg-overlay" ng-show="$ctrl.errorState">
<!-- indicate that some error occurred -->
<div class="msg" ng-show="$ctrl.errorState">
<span class="text-danger">
<span class="fa fa-warning"></span> {{ "ERRORS.UNKNOWN" | translate }}
</span>
</div>
</div>
12 changes: 6 additions & 6 deletions client/test/e2e/patient/registry.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('Patient Registry UI Grid ', function () {
dateBirthFrom : new Date('2016-05-01'),
dateBirthTo : new Date('2016-05-16'),
dateBirthFrom2 : new Date('1960-06-30'),
dateBirthTo2 : new Date('2016-05-16')
dateBirthTo2 : new Date('2016-05-16')
};

var grid = element(by.id('patient-registry'));
Expand Down Expand Up @@ -52,7 +52,7 @@ describe('Patient Registry UI Grid ', function () {

// set the gender of the patient
element(by.id('male')).click();

// submit the page to the server
FU.buttons.submit();

Expand All @@ -65,7 +65,7 @@ describe('Patient Registry UI Grid ', function () {
FU.buttons.search();
FU.input('ModalCtrl.patient.name', paramResearch.name1);
element(by.id('week')).click();

// submit the page to the server
FU.buttons.submit();
// The Grid should be have 1 visible rows
Expand All @@ -78,7 +78,7 @@ describe('Patient Registry UI Grid ', function () {
element(by.id('year')).click();
// set the gender of the patient
element(by.id('female')).click();

// submit the page to the server
FU.buttons.submit();
// The Grid should be have 0 visible rows
Expand All @@ -93,7 +93,7 @@ describe('Patient Registry UI Grid ', function () {
components.dateEditor.set(paramResearch.dateRegistrationTo, 'date-registration-to', '.bhima-title');
components.dateEditor.set(paramResearch.dateBirthFrom, 'date-birth-from', '.bhima-title');
components.dateEditor.set(paramResearch.dateBirthTo, 'date-birth-to', '.bhima-title');

// submit the page to the server
FU.buttons.submit();
// The Grid should be have 0 visible rows
Expand All @@ -108,7 +108,7 @@ describe('Patient Registry UI Grid ', function () {

components.dateEditor.set(paramResearch.dateBirthFrom2, 'date-birth-from', '.bhima-title');
components.dateEditor.set(paramResearch.dateBirthTo2, 'date-birth-to', '.bhima-title');

// submit the page to the server
FU.buttons.submit();
// The Grid should be have # visible rows
Expand Down

0 comments on commit b944ef6

Please sign in to comment.