2015-11-16 17:49:35 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Template: Worklist
|
|
|
|
|
|
*
|
|
|
|
|
|
* This is the main component of the Worklist package
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
// Define the ViewerData global object
|
|
|
|
|
|
// If there is currently any Session data for this object,
|
|
|
|
|
|
// use this to repopulate the variable
|
2015-10-23 14:08:51 +02:00
|
|
|
|
ViewerData = Session.get('ViewerData') || {};
|
|
|
|
|
|
|
2015-11-16 17:49:35 +01:00
|
|
|
|
// Define the StudyMetaData object. This is used as a cache
|
|
|
|
|
|
// to store study meta data information to prevent unnecessary
|
|
|
|
|
|
// calls to the server
|
|
|
|
|
|
var StudyMetaData = {};
|
|
|
|
|
|
|
|
|
|
|
|
// Create the WorklistTabs collection
|
|
|
|
|
|
WorklistTabs = new Meteor.Collection(null);
|
|
|
|
|
|
|
2015-11-17 18:17:51 +01:00
|
|
|
|
// Create the WorklistStudies collection
|
|
|
|
|
|
WorklistStudies = new Meteor.Collection(null);
|
|
|
|
|
|
|
2015-11-16 17:49:35 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Retrieves study metadata using a server call, and fires a callback
|
|
|
|
|
|
* when completed.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @params {string} studyInstanceUid The UID of the Study to be retrieved
|
|
|
|
|
|
* @params {function} doneCallback The callback function to be executed when the study retrieval has finished
|
|
|
|
|
|
*/
|
2015-10-30 12:21:59 +01:00
|
|
|
|
getStudyMetadata = function(studyInstanceUid, doneCallback) {
|
|
|
|
|
|
log.info('worklistStudy getStudyMetadata');
|
2015-10-13 14:14:50 +02:00
|
|
|
|
|
2015-11-16 17:49:35 +01:00
|
|
|
|
// If the StudyMetaData cache already has data related to this
|
|
|
|
|
|
// studyInstanceUid, then we should fire the doneCallback with this data
|
|
|
|
|
|
// and stop here.
|
|
|
|
|
|
var study = StudyMetaData[studyInstanceUid];
|
|
|
|
|
|
if (study) {
|
2015-10-30 12:21:59 +01:00
|
|
|
|
doneCallback(study);
|
|
|
|
|
|
return;
|
2015-10-20 15:45:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-11-16 17:49:35 +01:00
|
|
|
|
// If no study metadata is in the cache variable, we need to retrieve it from
|
|
|
|
|
|
// the server with a call.
|
2015-10-30 12:21:59 +01:00
|
|
|
|
Meteor.call('GetStudyMetadata', studyInstanceUid, function(error, study) {
|
2015-12-15 22:41:04 +01:00
|
|
|
|
if (error) {
|
|
|
|
|
|
log.warn(error);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2015-11-16 17:49:35 +01:00
|
|
|
|
// Once we have retrieved the data, we sort the series' by series
|
|
|
|
|
|
// and instance number in ascending order
|
2015-10-30 12:21:59 +01:00
|
|
|
|
sortStudy(study);
|
2015-11-16 17:49:35 +01:00
|
|
|
|
|
|
|
|
|
|
// Then we store this data in the cache variable
|
2015-10-30 12:21:59 +01:00
|
|
|
|
StudyMetaData[studyInstanceUid] = study;
|
2015-10-20 15:45:27 +02:00
|
|
|
|
|
2015-11-16 17:49:35 +01:00
|
|
|
|
// Finally, we fire the doneCallback with this study meta data
|
2015-10-30 12:21:59 +01:00
|
|
|
|
doneCallback(study);
|
2016-01-25 22:35:53 +01:00
|
|
|
|
|
|
|
|
|
|
// Temporary: Testing out the Clinical Meteor HIPAA Audit log
|
|
|
|
|
|
if (Meteor.user()) {
|
|
|
|
|
|
log.info('Adding access to HIPAA Log');
|
|
|
|
|
|
var hipaaEvent = {
|
|
|
|
|
|
eventType: "access",
|
|
|
|
|
|
userId: Meteor.userId(),
|
|
|
|
|
|
userName: Meteor.user().profile.fullName,
|
|
|
|
|
|
collectionName: "Studies",
|
|
|
|
|
|
recordId: studyInstanceUid,
|
|
|
|
|
|
patientId: null,
|
|
|
|
|
|
patientName: null
|
|
|
|
|
|
};
|
|
|
|
|
|
HipaaLogger.logEvent(hipaaEvent);
|
|
|
|
|
|
}
|
2015-10-30 12:21:59 +01:00
|
|
|
|
});
|
|
|
|
|
|
};
|
2015-10-29 13:50:28 +01:00
|
|
|
|
|
2015-11-16 17:49:35 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Switches to a new tab in the tabbed worklist container
|
|
|
|
|
|
* This function renders either the Worklist or the Viewer template with new data.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param contentId The unique ID of the tab to be switched to
|
|
|
|
|
|
*/
|
2015-10-30 12:21:59 +01:00
|
|
|
|
switchToTab = function(contentId) {
|
2016-01-12 21:56:07 +01:00
|
|
|
|
log.info('Switching to tab: ' + contentId);
|
2015-10-29 13:50:28 +01:00
|
|
|
|
|
2015-11-16 17:49:35 +01:00
|
|
|
|
// Use Bootstrap's Tab JavaScript to show the contents of the current tab
|
|
|
|
|
|
// Unless it is the worklist, it is currently an empty div
|
2015-10-23 14:08:51 +02:00
|
|
|
|
$('.tabTitle a[data-target="#' + contentId + '"]').tab('show');
|
2015-10-20 15:45:27 +02:00
|
|
|
|
|
2015-11-16 17:49:35 +01:00
|
|
|
|
// Remove any previous Viewers from the DOM
|
2016-01-12 21:56:07 +01:00
|
|
|
|
$('#viewer').remove();
|
2015-10-20 15:45:27 +02:00
|
|
|
|
|
2015-11-16 17:49:35 +01:00
|
|
|
|
// Update the 'activeContentId' variable in Session
|
2016-01-12 21:56:07 +01:00
|
|
|
|
Session.set('activeContentId', contentId);
|
2015-11-16 17:49:35 +01:00
|
|
|
|
|
|
|
|
|
|
// If we are switching to the Worklist tab, reset any CSS styles
|
|
|
|
|
|
// that have been applied to prevent scrolling in the Viewer.
|
|
|
|
|
|
// Then stop here, since nothing needs to be re-rendered.
|
2015-10-23 14:08:51 +02:00
|
|
|
|
if (contentId === 'worklistTab') {
|
2015-10-20 15:45:27 +02:00
|
|
|
|
document.body.style.overflow = null;
|
|
|
|
|
|
document.body.style.height = null;
|
|
|
|
|
|
document.body.style.minWidth = null;
|
|
|
|
|
|
document.body.style.position = null;
|
2015-10-30 12:21:59 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-11-16 17:49:35 +01:00
|
|
|
|
// Get tab content container given the contentId string
|
|
|
|
|
|
// If no such container exists, stop here because something is wrong
|
2016-01-12 21:56:07 +01:00
|
|
|
|
var container = $('.tab-content').find('#' + contentId).get(0);
|
2015-10-30 12:21:59 +01:00
|
|
|
|
if (!container) {
|
2015-11-16 17:49:35 +01:00
|
|
|
|
log.warn('No container present with the contentId: ' + contentId);
|
2015-10-30 12:21:59 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2015-11-16 17:49:35 +01:00
|
|
|
|
|
|
|
|
|
|
// Use the stored ViewerData global object to retrieve the studyInstanceUid
|
|
|
|
|
|
// related to this tab
|
2015-10-30 12:21:59 +01:00
|
|
|
|
var studyInstanceUid = ViewerData[contentId].studyInstanceUid;
|
2015-11-16 17:49:35 +01:00
|
|
|
|
|
|
|
|
|
|
// Attempt to retrieve the meta data (it might be cached)
|
2015-10-30 12:21:59 +01:00
|
|
|
|
getStudyMetadata(studyInstanceUid, function(study) {
|
2015-11-16 17:49:35 +01:00
|
|
|
|
|
|
|
|
|
|
// Once we have the study data, store it in a structure with
|
|
|
|
|
|
// any other saved data about this tab (e.g. layout structure)
|
2015-10-30 12:21:59 +01:00
|
|
|
|
var data = {
|
|
|
|
|
|
viewportRows: ViewerData[contentId].viewportRows,
|
|
|
|
|
|
viewportColumns: ViewerData[contentId].viewportColumns,
|
|
|
|
|
|
contentId: contentId,
|
2016-01-12 21:56:07 +01:00
|
|
|
|
studies: [ study ]
|
2015-10-30 12:21:59 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
2015-12-15 22:41:04 +01:00
|
|
|
|
if (ViewerData[contentId].studies && ViewerData[contentId].studies.length) {
|
|
|
|
|
|
data.studies = ViewerData[contentId].studies;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-11-16 17:49:35 +01:00
|
|
|
|
// Remove the loading text template that is inside the tab container by default
|
2016-01-12 21:56:07 +01:00
|
|
|
|
container.innerHTML = '';
|
2015-11-16 17:49:35 +01:00
|
|
|
|
|
|
|
|
|
|
// Use Blaze to render the Viewer Template into the container
|
2015-10-20 15:45:27 +02:00
|
|
|
|
UI.renderWithData(Template.viewer, data, container);
|
2015-11-16 17:49:35 +01:00
|
|
|
|
|
|
|
|
|
|
// Retrieve the DOM element of the viewer
|
2016-01-12 21:56:07 +01:00
|
|
|
|
var imageViewer = $('#viewer');
|
2015-11-16 17:49:35 +01:00
|
|
|
|
|
|
|
|
|
|
// If it is present in the DOM (it should be), then apply
|
|
|
|
|
|
// styles to prevent page scrolling and overscrolling on mobile devices
|
2015-10-20 15:45:27 +02:00
|
|
|
|
if (imageViewer) {
|
2016-01-12 21:56:07 +01:00
|
|
|
|
document.body.style.overflow = 'hidden';
|
2015-10-20 15:45:27 +02:00
|
|
|
|
document.body.style.height = '100%';
|
|
|
|
|
|
document.body.style.width = '100%';
|
|
|
|
|
|
document.body.style.minWidth = 0;
|
|
|
|
|
|
document.body.style.position = 'fixed'; // Prevent overscroll on mobile devices
|
2015-10-13 14:14:50 +02:00
|
|
|
|
}
|
2015-10-30 12:21:59 +01:00
|
|
|
|
});
|
2015-10-20 15:45:27 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
2015-11-16 17:49:35 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Opens a new tab in the tabbed worklist environment using
|
|
|
|
|
|
* a given study and new tab title.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param studyInstanceUid The UID of the Study to be opened
|
|
|
|
|
|
* @param title The title to be used for the tab heading
|
|
|
|
|
|
*/
|
2015-11-06 12:36:38 +01:00
|
|
|
|
openNewTab = function(studyInstanceUid, title) {
|
2015-11-16 17:49:35 +01:00
|
|
|
|
// Generate a unique ID to represent this tab
|
|
|
|
|
|
// We can't just use the Mongo entry ID because
|
|
|
|
|
|
// then it will change after hot-reloading.
|
2015-11-06 12:36:38 +01:00
|
|
|
|
var contentid = generateUUID();
|
2015-11-16 17:49:35 +01:00
|
|
|
|
|
|
|
|
|
|
// Create a new entry in the WorklistTabs Collection
|
|
|
|
|
|
WorklistTabs.insert({
|
2015-11-06 12:36:38 +01:00
|
|
|
|
title: title,
|
|
|
|
|
|
contentid: contentid,
|
2015-11-16 17:49:35 +01:00
|
|
|
|
active: false
|
|
|
|
|
|
});
|
2015-11-06 12:36:38 +01:00
|
|
|
|
|
2015-11-16 17:49:35 +01:00
|
|
|
|
// Update the ViewerData global object
|
2015-11-06 12:36:38 +01:00
|
|
|
|
ViewerData[contentid] = {
|
|
|
|
|
|
title: title,
|
|
|
|
|
|
contentid: contentid,
|
|
|
|
|
|
studyInstanceUid: studyInstanceUid
|
|
|
|
|
|
};
|
2015-11-16 17:49:35 +01:00
|
|
|
|
|
|
|
|
|
|
// Switch to the new tab
|
2015-11-06 12:36:38 +01:00
|
|
|
|
switchToTab(contentid);
|
2015-10-30 12:21:59 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Template.worklist.onRendered(function() {
|
|
|
|
|
|
// If there is a tab set as active in the Session,
|
|
|
|
|
|
// switch to that now.
|
2016-01-12 21:56:07 +01:00
|
|
|
|
var contentId = Session.get('activeContentId');
|
2015-10-30 12:21:59 +01:00
|
|
|
|
if (contentId) {
|
|
|
|
|
|
switchToTab(contentId);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2015-10-23 14:08:51 +02:00
|
|
|
|
|
2015-10-30 12:21:59 +01:00
|
|
|
|
Template.worklist.helpers({
|
2015-11-16 17:49:35 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Returns the current set of Worklist Tabs
|
|
|
|
|
|
* @returns Meteor.Collection The current state of the WorklistTabs Collection
|
|
|
|
|
|
*/
|
2016-01-12 21:56:07 +01:00
|
|
|
|
worklistTabs: function() {
|
2015-11-16 17:49:35 +01:00
|
|
|
|
return WorklistTabs.find();
|
2016-01-12 21:56:07 +01:00
|
|
|
|
},
|
2015-10-30 12:21:59 +01:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
Template.worklist.events({
|
2015-11-03 22:56:12 +01:00
|
|
|
|
'click #tablist a[data-toggle="tab"]': function(e) {
|
2015-11-06 15:29:09 +01:00
|
|
|
|
// If this tab is already active, do nothing
|
|
|
|
|
|
var tabButton = $(e.currentTarget);
|
|
|
|
|
|
var tabTitle = tabButton.parents('.tabTitle');
|
2016-01-12 21:56:07 +01:00
|
|
|
|
if (tabTitle.hasClass('active')) {
|
2015-11-06 15:29:09 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Otherwise, switch to the tab
|
2016-01-12 21:56:07 +01:00
|
|
|
|
var contentId = tabButton.data('target').replace('#', '');
|
2015-10-23 14:08:51 +02:00
|
|
|
|
switchToTab(contentId);
|
2015-10-30 12:21:59 +01:00
|
|
|
|
}
|
2016-01-12 21:56:07 +01:00
|
|
|
|
});
|