Remove some subscription / reactivity / caching bugs and the unnecessary PatientLocations collection

This commit is contained in:
Erik Ziegler 2016-02-09 23:46:04 +01:00
parent 7d42456f57
commit 9cde04bcfe
43 changed files with 321 additions and 234 deletions

View File

@ -1,16 +1,18 @@
<template name="viewer"> <template name="viewer">
{{#if Template.subscriptionsReady}}
<div id="viewer"> <div id="viewer">
{{>confirmDeleteDialog}} {{>confirmDeleteDialog}}
{{>lesionLocationDialog}} {{>lesionLocationDialog}}
{{>nonTargetLesionDialog}} {{>nonTargetLesionDialog}}
{{>nonTargetResponseDialog}} {{>nonTargetResponseDialog}}
{{>timepointTextDialog}} {{>conformanceCheckFeedback}}
{{ >conformanceCheckFeedback }}
{{>hidingPanel}} {{>hidingPanel}}
<div id="viewportAndLesionTable"> <div id="viewportAndLesionTable">
{{>viewerMain }} {{>viewerMain }}
{{>lesionTable }} {{>lesionTable }}
</div> </div>
</div> </div>
{{else}}
{{>loadingText}}
{{/if}}
</template> </template>

View File

@ -1,9 +1,14 @@
Session.setDefault('activeViewport', false); Session.setDefault('activeViewport', false);
ViewerStudies = new Meteor.Collection(null);
ViewerStudies._debugName = 'ViewerStudies';
Template.viewer.onCreated(function() { Template.viewer.onCreated(function() {
// Attach the Window resize listener // Attach the Window resize listener
$(window).on('resize', handleResize); $(window).on('resize', handleResize);
ValidationErrors.remove({});
var self = this; var self = this;
var firstMeasurementsActivated = false; var firstMeasurementsActivated = false;
var contentId = this.data.contentId; var contentId = this.data.contentId;
@ -97,10 +102,11 @@ Template.viewer.onCreated(function() {
Session.set('activeViewport', ViewerData[contentId].activeViewport || false); Session.set('activeViewport', ViewerData[contentId].activeViewport || false);
// Set lesion tool buttons as disabled if pixel spacing is not available for active element // Set lesion tool buttons as disabled if pixel spacing is not available for active element
this.autorun(pixelSpacingAutorunCheck); self.autorun(pixelSpacingAutorunCheck);
// Update the ViewerStudies collection with the loaded studies // Update the ViewerStudies collection with the loaded studies
ViewerStudies = new Meteor.Collection(null); ViewerStudies.remove({});
this.data.studies.forEach(function(study) { this.data.studies.forEach(function(study) {
study.selected = true; study.selected = true;
ViewerStudies.insert(study); ViewerStudies.insert(study);
@ -110,11 +116,17 @@ Template.viewer.onCreated(function() {
Session.set('patientId', patientId); Session.set('patientId', patientId);
self.autorun(function() { self.autorun(function() {
var patientId = Session.get('patientId'); var dataContext = Template.currentData();
self.subscribe('singlePatientTimepoints', patientId); self.subscribe('singlePatientAssociatedStudies', dataContext.studies[0].patientId);
self.subscribe('singlePatientMeasurements', patientId); self.subscribe('singlePatientTimepoints', dataContext.studies[0].patientId);
self.subscribe('singlePatientMeasurements', dataContext.studies[0].patientId);
var subscriptionsReady = self.subscriptionsReady();
console.log('autorun viewer.js. Ready: ' + subscriptionsReady);
if (subscriptionsReady) {
TrialResponseCriteria.validateAllDelayed();
if (self.subscriptionsReady()) {
ViewerStudies.find().observe({ ViewerStudies.find().observe({
added: function(study) { added: function(study) {
// Find the relevant timepoint given the newly added study // Find the relevant timepoint given the newly added study
@ -128,7 +140,7 @@ Template.viewer.onCreated(function() {
log.warn('Study added to Viewer has not been associated!'); log.warn('Study added to Viewer has not been associated!');
return; return;
} }
// Update the added document with its related timepointId // Update the added document with its related timepointId
ViewerStudies.update(study._id, { ViewerStudies.update(study._id, {
$set: { $set: {

View File

@ -0,0 +1,68 @@
Meteor.startup(function() {
var debug = false;
if (debug === true) {
// http://www.meteorpedia.com/read/Debugging_Reactivity
Meteor.autorun(function(computation) {
computation.onInvalidate(function() {
console.trace();
});
});
var wrappedFind = Meteor.Collection.prototype.find;
Meteor.Collection.prototype.find = function() {
var cursor = wrappedFind.apply(this, arguments);
var collectionName = this._name || this._debugName;
/*cursor.observeChanges({
added: function(id, fields) {
console.log(collectionName, 'added', id, fields);
},
changed: function(id, fields) {
console.log(collectionName, 'changed', id, fields);
},
movedBefore: function(id, before) {
console.log(collectionName, 'movedBefore', id, before);
},
removed: function(id) {
console.log(collectionName, 'removed', id);
}
});*/
cursor.observe({
added: function(data) {
console.log(collectionName, 'added', data);
},
changed: function(data) {
console.log(collectionName, 'changed', data);
},
removed: function(data) {
console.log(collectionName, 'removed', data);
}
});
return cursor;
};
function logRenders() {
Object.keys(Template).forEach(function(name) {
if (name.indexOf('_') > -1) {
return;
}
var template = Template[name];
var oldRender = template.rendered;
var counter = 0;
template.rendered = function() {
console.log(name, 'render count: ', ++counter);
oldRender && oldRender.apply(this, arguments);
};
});
}
logRenders();
}
});

View File

@ -1,4 +1,5 @@
LesionLocations = new Meteor.Collection(null); LesionLocations = new Meteor.Collection(null);
LesionLocations._debugName = 'LesionLocations';
var organGroups = [ var organGroups = [
'Abdominal/Chest Wall', 'Abdominal/Chest Wall',

View File

@ -1,4 +1,5 @@
LocationResponses = new Meteor.Collection(null); LocationResponses = new Meteor.Collection(null);
LocationResponses._debugName = 'LocationResponses';
LocationResponses.insert({ LocationResponses.insert({
text: 'Complete response', text: 'Complete response',

View File

@ -1 +0,0 @@
PatientLocations = new Meteor.Collection(null);

View File

@ -194,11 +194,10 @@
y: mouseEventData.currentPoints.image.y - 70, y: mouseEventData.currentPoints.image.y - 70,
pointNearHandle: pointNearTextBox, pointNearHandle: pointNearTextBox,
active: false, active: false,
movesIndependently: true, movesIndependently: false,
drawnIndependently: true, drawnIndependently: true,
allowedOutsideImage: true allowedOutsideImage: true
}, },
perpendicularStart: { perpendicularStart: {
x: mouseEventData.currentPoints.image.x, x: mouseEventData.currentPoints.image.x,
y: mouseEventData.currentPoints.image.y, y: mouseEventData.currentPoints.image.y,
@ -208,7 +207,6 @@
drawnIndependently: true, drawnIndependently: true,
index: 2 index: 2
}, },
perpendicularEnd: { perpendicularEnd: {
x: mouseEventData.currentPoints.image.x, x: mouseEventData.currentPoints.image.x,
y: mouseEventData.currentPoints.image.y, y: mouseEventData.currentPoints.image.y,
@ -217,7 +215,6 @@
drawnIndependently: true, drawnIndependently: true,
index: 3 index: 3
} }
}, },
imageId: imageId, imageId: imageId,
seriesInstanceUid: seriesInstanceUid, seriesInstanceUid: seriesInstanceUid,

View File

@ -141,7 +141,7 @@
y: mouseEventData.currentPoints.image.y - 50, y: mouseEventData.currentPoints.image.y - 50,
pointNearHandle: pointNearTextBox, pointNearHandle: pointNearTextBox,
active: false, active: false,
movesIndependently: true, movesIndependently: false,
drawnIndependently: true, drawnIndependently: true,
allowedOutsideImage: true allowedOutsideImage: true
} }

View File

@ -59,18 +59,53 @@ Template.associationModal.events({
// Sort the study dates, so we can get a range for these values // Sort the study dates, so we can get a range for these values
studyDates = studyDates.sort(); studyDates = studyDates.sort();
// Create a new timepoint to represent the (baseline or follow-up) studies // Check if these studies are already associated with an existing Timepoint
var timepoint = { var existingTimepoint;
timepointType: timepointType, if (timepointType === 'baseline') {
timepointId: uuid.new(), // If we're trying to associate them to the Baseline, we don't need to
studyInstanceUids: studyInstanceUids, // check if the studyInstanceUids are already associated with anything else
patientId: relatedStudies[0].patientId, // TODO: Revisit this (Should timepoints be related to patientId?) existingTimepoint = Timepoints.findOne({
earliestDate: studyDates[0].format('YYYYMMDD'), patientId: relatedStudies[0].patientId,
latestDate: studyDates[studyDates.length - 1].format('YYYYMMDD') timepointType: 'baseline'
}; });
} else {
// If we're trying to associate them to a Follow-up, we should check if any
// of them are already part of a Follow-up (e.g. Follow-up 1), so that
// the rest will also be associated with Follow-up 1.
existingTimepoint = Timepoints.findOne({
patientId: relatedStudies[0].patientId,
studyInstanceUids: {
$in: studyInstanceUids
}
});
}
// Insert this timepoint into the Timepoints Collection var timepointId;
Timepoints.insert(timepoint); if (existingTimepoint) {
// If these studies are already associated with an existing Timepoint,
// and the desired timepoint type is the same (e.g. Follow-up), update
// this Timepoint instead of creating a new one
Timepoints.update(existingTimepoint._id, {
$set: {
studyInstanceUids: studyInstanceUids
}
});
timepointId = existingTimepoint.timepointId;
} else {
// Create a new timepoint to represent the (baseline or follow-up) studies
var timepoint = {
timepointType: timepointType,
timepointId: uuid.new(),
studyInstanceUids: studyInstanceUids,
patientId: relatedStudies[0].patientId,
earliestDate: studyDates[0].format('YYYYMMDD'),
latestDate: studyDates[studyDates.length - 1].format('YYYYMMDD')
};
// Insert this timepoint into the Timepoints Collection
Timepoints.insert(timepoint);
timepointId = timepoint.timepointId;
}
// Loop through these studies to associate them with the newly created timepoint // Loop through these studies to associate them with the newly created timepoint
relatedStudies.forEach(function(study) { relatedStudies.forEach(function(study) {
@ -83,7 +118,7 @@ Template.associationModal.events({
// If a study already exists, update the entry with the new timepointId // If a study already exists, update the entry with the new timepointId
Studies.update(existingStudy._id, { Studies.update(existingStudy._id, {
$set: { $set: {
timepointId: timepoint.timepointId timepointId: timepointId
} }
}); });
} else { } else {
@ -93,7 +128,7 @@ Template.associationModal.events({
delete study._id; delete study._id;
// Attach the timepointId and insert it into the Studies Collection // Attach the timepointId and insert it into the Studies Collection
study.timepointId = timepoint.timepointId; study.timepointId = timepointId;
Studies.insert(study); Studies.insert(study);
} }
}); });

View File

@ -205,20 +205,6 @@ Template.lesionLocationDialog.events({
_id: selectedOptionId _id: selectedOptionId
}); });
var id;
var existingLocation = PatientLocations.findOne({
location: locationObj.location
});
if (existingLocation) {
id = existingLocation._id;
} else {
// Adds location data to PatientLocation and retrieve the location ID
id = PatientLocations.insert({
location: locationObj.location,
locationId: locationObj._id
});
}
// Add an ID value to the tool data to link it to the Measurements collection // Add an ID value to the tool data to link it to the Measurements collection
if (!measurementData.id) { if (!measurementData.id) {
measurementData.id = 'notready'; measurementData.id = 'notready';
@ -226,7 +212,6 @@ Template.lesionLocationDialog.events({
// Link locationUID with active lesion measurementData // Link locationUID with active lesion measurementData
measurementData.location = locationObj.location; measurementData.location = locationObj.location;
measurementData.locationId = locationObj.id; measurementData.locationId = locationObj.id;
measurementData.locationUID = id;
/// Set the isTarget value to true, since this is the target-lesion dialog callback /// Set the isTarget value to true, since this is the target-lesion dialog callback
measurementData.isTarget = true; measurementData.isTarget = true;
@ -241,8 +226,7 @@ Template.lesionLocationDialog.events({
$set: { $set: {
location: locationObj.location, location: locationObj.location,
locationId: locationObj.id, locationId: locationObj.id,
isNodal: locationObj.isNodal, isNodal: locationObj.isNodal
locationUID: id
} }
}); });
} }

View File

@ -31,10 +31,10 @@ function removeTimepointAssociations() {
return; return;
} }
// Update the Studies Collection to remove the link to this Timepoint // Remove this entry from the Studies Collection
Studies.update(study._id, { Meteor.call('removeAssociatedStudy', study._id, function(error) {
unset: { if (error) {
timepointId: '' log.warn(error);
} }
}); });

View File

@ -70,27 +70,28 @@ function getLesionLocationCallback(measurementData, eventData) {
// Find out if this lesion number is already added in the lesion manager for another timepoint // Find out if this lesion number is already added in the lesion manager for another timepoint
// If it is, disable selector location // If it is, disable selector location
var locationUID = LesionManager.lesionNumberExists(measurementData); var locationId = LesionManager.lesionNumberExists(measurementData);
if (locationUID) { if (locationId) {
// Add an ID value to the tool data to link it to the Measurements collection // Add an ID value to the tool data to link it to the Measurements collection
measurementData.id = 'notready'; measurementData.id = 'notready';
measurementData.locationUID = locationUID; measurementData.locationId = locationId;
// Disable the selection of a new location // Disable the selection of a new location
disableLocationSelection(measurementData.locationUID); disableLocationSelection(measurementData.locationId);
} }
// Disable selector location to prevent selecting a new location // Disable selector location to prevent selecting a new location
function disableLocationSelection(locationUID) { function disableLocationSelection(locationId) {
var locationName = LesionManager.getLocationName(locationUID); var locationObject = LesionLocations.findOne({
selectorLocation.find('option').each(function() { id: locationId
if ($(this).text() === locationName) { });
// Select location in locations dropdown list
selectorLocation.find('option').eq($(this).index()).prop('selected', true); if (!locationObject) {
} return;
}); }
selectorLocation.find('option[value="' + locationObject._id + '"]').prop('selected', true);
selectorLocation.prop('disabled', true); selectorLocation.prop('disabled', true);
} }
@ -273,26 +274,12 @@ Template.nonTargetLesionDialog.events({
_id: selectedOptionId _id: selectedOptionId
}); });
var id;
var existingLocation = PatientLocations.findOne({
location: locationObj.location
});
if (existingLocation) {
id = existingLocation._id;
} else {
// Adds location data to PatientLocation and retrieve the location ID
id = PatientLocations.insert({
location: locationObj.location
});
}
if (measurementData.id) { if (measurementData.id) {
// Update the location data // Update the location data
Measurements.update(measurementData.id, { Measurements.update(measurementData.id, {
$set: { $set: {
location: locationObj.location, location: locationObj.location,
locationId: locationObj.id, locationId: locationObj.id,
locationUID: id
} }
}); });
} else { } else {
@ -300,9 +287,6 @@ Template.nonTargetLesionDialog.events({
measurementData.id = 'notready'; measurementData.id = 'notready';
} }
// Link locationUID with active lesion measurementData
measurementData.locationUID = id;
/// Set the isTarget value to true, since this is the target-lesion dialog callback /// Set the isTarget value to true, since this is the target-lesion dialog callback
measurementData.isTarget = false; measurementData.isTarget = false;

View File

@ -76,12 +76,7 @@ Template.studyDateList.events({
var loadingIndicator = selectBox.siblings('.loading'); var loadingIndicator = selectBox.siblings('.loading');
loadingIndicator.css('display', 'block'); loadingIndicator.css('display', 'block');
Meteor.call('GetStudyMetadata', studyInstanceUid, function(error, study) { getStudyMetadata(studyInstanceUid, function(study) {
if (error) {
log.warn(error);
return;
}
sortStudy(study); sortStudy(study);
// Hide the loading indicator // Hide the loading indicator

View File

@ -1,12 +0,0 @@
<template name="timepointTextDialog">
<div id="timepointTextDialog">
<div class="timepointDialogContentWrapper">
<div class="dialogContent">
<div class="timepointContent">
<input type="checkbox" id="checkBoxBaseline" value="ok">
<label for="checkBoxBaseline">Baseline</label>
</div>
</div>
</div>
</div>
</template>

View File

@ -1,25 +0,0 @@
#timepointTextDialog
display: none
position: absolute
z-index: 99
.timepointDialogContentWrapper
position: absolute
width: 100px
height: 30px
padding: 5px
background-color: rgba(255,255,255,1)
border-top-left-radius: 5px
border-top-right-radius: 5px
.dialogContent
margin: 0 auto
float: none
padding: 0
.timepointContent
font-size: 16px
#checkBoxBaseline
height: 16px
width: 16px

View File

@ -1,20 +1,3 @@
/**
* Retrieve a location name (e.g. Liver Right) from the
* PatientLocations Collection by id, if it exists. Otherwise,
* return an empty string.
*
* @param id
* @returns {*|string}
*/
function getLocationName(id) {
var locationObject = PatientLocations.findOne(id);
if (!locationObject || !locationObject.location) {
return '';
}
return locationObject.location;
}
/** /**
* Update the Timepoint object for a specific Measurement. * Update the Timepoint object for a specific Measurement.
* If no measurement exists yet, one will be created. * If no measurement exists yet, one will be created.
@ -33,7 +16,6 @@ function updateLesionData(lesionData) {
return; return;
} }
var timepoint = Timepoints.findOne({ var timepoint = Timepoints.findOne({
timepointId: study.timepointId timepointId: study.timepointId
}); });
@ -84,7 +66,7 @@ function updateLesionData(lesionData) {
// Retrieve the location name given the locationUID // Retrieve the location name given the locationUID
if (lesionData.locationUID !== undefined) { if (lesionData.locationUID !== undefined) {
var locationObj = PatientLocations.findOne({ var locationObj = LesionLocations.findOne({
locationUID: lesionData.locationUID locationUID: lesionData.locationUID
}); });
@ -191,12 +173,11 @@ function lesionNumberExists(lesionData) {
return; return;
} }
return measurement.locationUID; return measurement.locationId;
} }
LesionManager = { LesionManager = {
updateLesionData: updateLesionData, updateLesionData: updateLesionData,
getNewLesionNumber: getNewLesionNumber, getNewLesionNumber: getNewLesionNumber,
lesionNumberExists: lesionNumberExists, lesionNumberExists: lesionNumberExists
getLocationName: getLocationName
}; };

View File

@ -50,7 +50,7 @@ function RECIST(image) {
shortestDiameter: { shortestDiameter: {
numericality: { numericality: {
greaterThanOrEqualTo: 15, greaterThanOrEqualTo: 15,
message: '^Nodal target lesions must be >= %{count} mm short axis' //message: '^Nodal target lesions must be >= %{count} mm short axis'
} }
} }
}; };
@ -72,7 +72,7 @@ function RECIST(image) {
totalLesionBurden: { totalLesionBurden: {
numericality: { numericality: {
greaterThanOrEqualTo: 2, // TODO: Check this, the value wasn't specified! greaterThanOrEqualTo: 2, // TODO: Check this, the value wasn't specified!
message: '^Total lesion burden (SPD target lesions + SPD new lesions) should be greater than %{count}.' //message: '^Total lesion burden (SPD target lesions + SPD new lesions) should be greater than %{count}.'
} }
} }
}; };
@ -126,7 +126,7 @@ function RECIST(image) {
longestDiameter: { longestDiameter: {
numericality: { numericality: {
greaterThanOrEqualTo: 20, greaterThanOrEqualTo: 20,
message: '^Extranodal lesions must be >= %{count} mm on chest X-ray' //message: '^Extranodal lesions must be >= %{count} mm on chest X-ray'
} }
} }
}; };
@ -135,7 +135,7 @@ function RECIST(image) {
longestDiameter: { longestDiameter: {
numericality: { numericality: {
greaterThanOrEqualTo: 10, greaterThanOrEqualTo: 10,
message: '^Extranodal target lesions must be >= %{count} mm long axis' //message: '^Extranodal target lesions must be >= %{count} mm long axis'
} }
} }
}; };
@ -340,6 +340,9 @@ getTrialCriteriaConstraints = function(criteriaType, imageId) {
// Otherwise, retrieve the series metaData to identify the modality of the image // Otherwise, retrieve the series metaData to identify the modality of the image
var seriesMetaData = cornerstoneTools.metaData.get('series', imageId); var seriesMetaData = cornerstoneTools.metaData.get('series', imageId);
if (!seriesMetaData) {
return;
}
// TODO: Get the rest of the metaData that has already been loaded by Cornerstone // TODO: Get the rest of the metaData that has already been loaded by Cornerstone
var image = {}; var image = {};
@ -363,7 +366,6 @@ getTrialCriteriaConstraints = function(criteriaType, imageId) {
}); });
if (!study) { if (!study) {
log.warn('No study/timepoint association.');
return; return;
} }

View File

@ -1,5 +1,6 @@
// Create a client-only Collection to store our Validation Errors // Create a client-only Collection to store our Validation Errors
ValidationErrors = new Meteor.Collection(null); ValidationErrors = new Meteor.Collection(null);
ValidationErrors._debugName = 'ValidationErrors';
// Set Validate.js Library's default options // Set Validate.js Library's default options
validate.options = { validate.options = {
@ -216,8 +217,8 @@ function validateSingleMeasurement(measurementData) {
var criteriaType = Session.get('TrialResponseAssessmentCriteria'); var criteriaType = Session.get('TrialResponseAssessmentCriteria');
var currentConstraints = getTrialCriteriaConstraints(criteriaType, measurementData.imageId); var currentConstraints = getTrialCriteriaConstraints(criteriaType, measurementData.imageId);
// If we have no relevant constraints, stop here
if (!currentConstraints) { if (!currentConstraints) {
log.warn('No relevant contraints could be applied');
return; return;
} }
@ -236,6 +237,11 @@ function validateSingleMeasurement(measurementData) {
// Find the specific measurement data for this Measurement at this Timepoint // Find the specific measurement data for this Measurement at this Timepoint
var currentMeasurement = measurement.timepoints[timepointId]; var currentMeasurement = measurement.timepoints[timepointId];
// Return here if the measurement was removed during validation
if (!currentMeasurement) {
return;
}
// Include target and nodal flags on the timepoint-specific data so it is easier to validate // Include target and nodal flags on the timepoint-specific data so it is easier to validate
// TODO: Rethink what to pass to assessSingleMeasurement? // TODO: Rethink what to pass to assessSingleMeasurement?
currentMeasurement.isTarget = measurement.isTarget; currentMeasurement.isTarget = measurement.isTarget;
@ -256,10 +262,11 @@ function validateGroups() {
var criteriaType = Session.get('TrialResponseAssessmentCriteria'); var criteriaType = Session.get('TrialResponseAssessmentCriteria');
Timepoints.find().forEach(function(timepoint) { Timepoints.find().forEach(function(timepoint) {
// TODO: Criteria for the specific image are retrieved from the general set of criteria. // Criteria for the specific image are retrieved from the general set of criteria.
// - The acquisitionSliceThickness, for example, may be pulled from the image metadata
// - The organ in question, e.g. Chest X-ray, may determine the exact specifications for the current trial criteria
var currentConstraints = getTrialCriteriaConstraints(criteriaType); var currentConstraints = getTrialCriteriaConstraints(criteriaType);
if (!currentConstraints) {
return;
}
// Retrieve the current constraints which apply to the specific Timepoint type // Retrieve the current constraints which apply to the specific Timepoint type
// (e.g. baseline, followup) that this Measurement is being edited on. // (e.g. baseline, followup) that this Measurement is being edited on.
@ -289,12 +296,9 @@ function validateAll() {
currentMeasurement.lesionNumber = measurement.lesionNumber; currentMeasurement.lesionNumber = measurement.lesionNumber;
currentMeasurement._id = measurement._id; currentMeasurement._id = measurement._id;
// TODO: Criteria for the specific image are retrieved from the general set of criteria. // Criteria for the specific image are retrieved from the general set of criteria.
// - The acquisitionSliceThickness, for example, may be pulled from the image metadata
// - The organ in question, e.g. Chest X-ray, may determine the exact specifications for the current trial criteria
var currentConstraints = getTrialCriteriaConstraints(criteriaType, currentMeasurement.imageId); var currentConstraints = getTrialCriteriaConstraints(criteriaType, currentMeasurement.imageId);
if (!currentConstraints) { if (!currentConstraints) {
log.warn('No relevant contraints could be applied');
return; return;
} }

View File

@ -12,6 +12,10 @@ activateMeasurements = function(element, measurementId, templateData, viewportIn
var timepointData = getTimepointObject(imageId); var timepointData = getTimepointObject(imageId);
var measurementData = Measurements.findOne(measurementId); var measurementData = Measurements.findOne(measurementId);
if (!timepointData) {
return;
}
var measurementAtTimepoint = measurementData.timepoints[timepointData.timepointId]; var measurementAtTimepoint = measurementData.timepoints[timepointData.timepointId];
if (!measurementAtTimepoint) { if (!measurementAtTimepoint) {
return; return;

View File

@ -5,11 +5,15 @@ clearTools = function() {
var toolStateKeys = Object.keys(toolState).slice(0); var toolStateKeys = Object.keys(toolState).slice(0);
var viewportElements = $('.imageViewerViewport').not('.empty'); var viewportElements = $('.imageViewerViewport').not('.empty');
var seriesInstanceUIds = []; // Holds seriesInstanceUId of imageViewerViewport elements var seriesInstanceUids = []; // Holds seriesInstanceUid of imageViewerViewport elements
viewportElements.each(function(index, element) { viewportElements.each(function(index, element) {
var enabledElement = cornerstone.getEnabledElement(element); var enabledElement = cornerstone.getEnabledElement(element);
var series = cornerstoneTools.metaData.get('series', enabledElement.image.imageId); var series = cornerstoneTools.metaData.get('series', enabledElement.image.imageId);
seriesInstanceUIds.push(series.seriesInstanceUid); if (!series) {
return;
}
seriesInstanceUids.push(series.seriesInstanceUid);
}); });
// Set null array for toolState data found by imageId and toolType // Set null array for toolState data found by imageId and toolType
@ -18,8 +22,12 @@ clearTools = function() {
var toolTypeData = toolState[imageId][toolType]; var toolTypeData = toolState[imageId][toolType];
if (toolTypeData && toolTypeData.data.length > 0) { if (toolTypeData && toolTypeData.data.length > 0) {
var series = cornerstoneTools.metaData.get('series', imageId); var series = cornerstoneTools.metaData.get('series', imageId);
// If seriesInstanceUid is found in seriesInstanceUIds, set toolState data as null if (!series) {
if (seriesInstanceUIds.indexOf(series.seriesInstanceUid) > -1) { return;
}
// If seriesInstanceUid is found in seriesInstanceUids, set toolState data as null
if (seriesInstanceUids.indexOf(series.seriesInstanceUid) > -1) {
toolState[imageId][toolType] = { toolState[imageId][toolType] = {
data: [] data: []
}; };

View File

@ -1,10 +1,10 @@
handleMeasurementAdded = function(e, eventData) { handleMeasurementAdded = function(e, eventData) {
log.info('CornerstoneToolsMeasurementAdded');
var measurementData = eventData.measurementData; var measurementData = eventData.measurementData;
switch (eventData.toolType) { switch (eventData.toolType) {
case 'nonTarget': case 'nonTarget':
case 'lesion': case 'lesion':
log.info('CornerstoneToolsMeasurementAdded');
LesionManager.updateLesionData(measurementData); LesionManager.updateLesionData(measurementData);
TrialResponseCriteria.validateDelayed(measurementData); TrialResponseCriteria.validateDelayed(measurementData);
break; break;

View File

@ -1,10 +1,10 @@
handleMeasurementModified = function(e, eventData) { handleMeasurementModified = function(e, eventData) {
log.info('CornerstoneToolsMeasurementModified');
var measurementData = eventData.measurementData; var measurementData = eventData.measurementData;
switch (eventData.toolType) { switch (eventData.toolType) {
case 'nonTarget': case 'nonTarget':
case 'lesion': case 'lesion':
log.info('CornerstoneToolsMeasurementModified');
LesionManager.updateLesionData(measurementData); LesionManager.updateLesionData(measurementData);
TrialResponseCriteria.validateDelayed(measurementData); TrialResponseCriteria.validateDelayed(measurementData);
break; break;

View File

@ -62,7 +62,7 @@ syncMeasurementAndToolData = function(data) {
measurementData.isDeleted = data.isDeleted; measurementData.isDeleted = data.isDeleted;
measurementData.location = data.location; measurementData.location = data.location;
measurementData.locationUID = data.locationUID; measurementData.locationUID = data.locationUID;
measurementData.patientId = patientId; measurementData.patientId = data.patientId;
measurementData.visible = data.visible; measurementData.visible = data.visible;
measurementData.active = data.active; measurementData.active = data.active;
measurementData.uid = data.uid; measurementData.uid = data.uid;

View File

@ -33,5 +33,8 @@ function dblClickOnStudy(data) {
return; return;
} }
// Add the Timepoint name to the Patient name to create the tab title
title += ' ' + getTimepointName(timepoint);
openNewTabWithTimepoint(timepoint.timepointId, title); openNewTabWithTimepoint(timepoint.timepointId, title);
} }

View File

@ -29,7 +29,6 @@ Package.onUse(function(api) {
// Client-side collections // Client-side collections
api.addFiles('client/collections/LesionLocations.js', 'client'); api.addFiles('client/collections/LesionLocations.js', 'client');
api.addFiles('client/collections/LocationResponses.js', 'client'); api.addFiles('client/collections/LocationResponses.js', 'client');
api.addFiles('client/collections/PatientLocations.js', 'client');
// Additional Custom Cornerstone Tools for Lesion Tracker // Additional Custom Cornerstone Tools for Lesion Tracker
api.addFiles('client/compatibility/lesionTool.js', 'client', { api.addFiles('client/compatibility/lesionTool.js', 'client', {
@ -108,9 +107,6 @@ Package.onUse(function(api) {
api.addFiles('client/components/nonTargetResponseDialog/nonTargetResponseDialog.styl', 'client'); api.addFiles('client/components/nonTargetResponseDialog/nonTargetResponseDialog.styl', 'client');
api.addFiles('client/components/nonTargetResponseDialog/nonTargetResponseDialog.js', 'client'); api.addFiles('client/components/nonTargetResponseDialog/nonTargetResponseDialog.js', 'client');
api.addFiles('client/components/timepointTextDialog/timepointTextDialog.html', 'client');
api.addFiles('client/components/timepointTextDialog/timepointTextDialog.styl', 'client');
api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.html', 'client'); api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.html', 'client');
api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.styl', 'client'); api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.styl', 'client');
api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.js', 'client'); api.addFiles('client/components/lesionTrackerWorklistStudy/lesionTrackerWorklistStudy.js', 'client');
@ -186,7 +182,6 @@ Package.onUse(function(api) {
api.export('ValidationErrors', 'client'); api.export('ValidationErrors', 'client');
api.export('LesionLocations', 'client'); api.export('LesionLocations', 'client');
api.export('LocationResponses', 'client'); api.export('LocationResponses', 'client');
api.export('PatientLocations', 'client');
// Export collections spanning both client and server // Export collections spanning both client and server
api.export('Measurements', [ 'client', 'server' ]); api.export('Measurements', [ 'client', 'server' ]);

View File

@ -12,6 +12,12 @@ Meteor.publish('studies', function() {
return Studies.find(); return Studies.find();
}); });
Meteor.publish('singlePatientAssociatedStudies', function(patientId) {
return Studies.find({
patientId: patientId
});
});
Meteor.publish('singlePatientMeasurements', function(patientId) { Meteor.publish('singlePatientMeasurements', function(patientId) {
return Measurements.find({ return Measurements.find({
patientId: patientId patientId: patientId

View File

@ -49,5 +49,8 @@ Meteor.methods({
}, },
removeTimepoint: function(id) { removeTimepoint: function(id) {
Timepoints.remove(id); Timepoints.remove(id);
},
removeAssociatedStudy: function(id) {
Studies.remove(id);
} }
}); });

View File

@ -28,10 +28,10 @@ Template.imageThumbnail.onRendered(function() {
}); });
Template.imageThumbnail.helpers({ Template.imageThumbnail.helpers({
'percentComplete': function() { percentComplete: function() {
var percentComplete = Session.get('CornerstoneThumbnailLoadProgress' + this.thumbnailIndex); var percentComplete = Session.get('CornerstoneThumbnailLoadProgress' + this.thumbnailIndex);
if (percentComplete && percentComplete !== 100) { if (percentComplete && percentComplete !== 100) {
return percentComplete + '%'; return percentComplete + '%';
} }
} }
}); });

View File

@ -1,11 +1,11 @@
Template.hangingProtocolButtons.helpers({ Template.hangingProtocolButtons.helpers({
'isNextAvailable': function() { isNextAvailable: function() {
var presentationGroup = Session.get('WindowManagerPresentationGroup'); var presentationGroup = Session.get('WindowManagerPresentationGroup');
var numPresentationGroups = WindowManager.getNumPresentationGroups(); var numPresentationGroups = WindowManager.getNumPresentationGroups();
return presentationGroup < numPresentationGroups; return presentationGroup < numPresentationGroups;
}, },
'isPreviousAvailable': function() { isPreviousAvailable: function() {
var presentationGroup = Session.get('WindowManagerPresentationGroup'); var presentationGroup = Session.get('WindowManagerPresentationGroup');
return presentationGroup > 1; return presentationGroup > 1;
} }
}); });

View File

@ -411,6 +411,10 @@ Meteor.startup(function() {
cornerstoneTools.magnify.setConfiguration(config); cornerstoneTools.magnify.setConfiguration(config);
}); });
Template.imageViewerViewport.onCreated(function() {
console.log('imageViewerViewport onCreated');
});
Template.imageViewerViewport.onRendered(function() { Template.imageViewerViewport.onRendered(function() {
var templateData = Template.currentData(); var templateData = Template.currentData();
log.info('imageViewerViewport onRendered'); log.info('imageViewerViewport onRendered');
@ -455,7 +459,7 @@ Template.imageViewerViewport.onRendered(function() {
// TODO: This code block might be refactored // TODO: This code block might be refactored
// Load previous measurement study when reloading a patient // Load previous measurement study when reloading a patient
if (!study) { if (!study) {
Meteor.call('GetStudyMetadata', this.data.studyInstanceUid, function(error, study) { getStudyMetadata(this.data.studyInstanceUid, function(study) {
// Once we have retrieved the data, we sort the series' by series // Once we have retrieved the data, we sort the series' by series
// and instance number in ascending order // and instance number in ascending order
if (!study) { if (!study) {
@ -466,7 +470,7 @@ Template.imageViewerViewport.onRendered(function() {
data.study = study; data.study = study;
setSeries(data, seriesInstanceUid, templateData); setSeries(data, seriesInstanceUid, templateData);
}); })
} }
data.study = study; data.study = study;
@ -482,7 +486,10 @@ Template.imageViewerViewport.onDestroyed(function() {
// Try to stop any currently playing clips // Try to stop any currently playing clips
// Otherwise the interval will continuously throw errors // Otherwise the interval will continuously throw errors
try { try {
cornerstoneTools.stopClip(element); var enabledElement = cornerstone.getEnabledElement(element);
if (enabledElement) {
cornerstoneTools.stopClip(element);
}
} catch (error) { } catch (error) {
log.warn(error); log.warn(error);
} }

View File

@ -1,4 +1,5 @@
ViewerWindows = new Meteor.Collection(null); ViewerWindows = new Meteor.Collection(null);
ViewerWindows._debugName = 'ViewerWindows';
Template.imageViewerViewports.helpers({ Template.imageViewerViewports.helpers({
height: function() { height: function() {
@ -10,14 +11,16 @@ Template.imageViewerViewports.helpers({
return 100 / viewportColumns; return 100 / viewportColumns;
}, },
viewerWindow: function() { viewerWindow: function() {
ViewerWindows = new Meteor.Collection(null); log.info('ViewerWindows');
//log.info(ViewerWindows.find().fetch());
ViewerWindows.remove({});
log.info("imageViewerViewports viewportArray"); log.info('imageViewerViewports viewportArray');
var viewportRows = this.viewportRows || 1; var viewportRows = this.viewportRows || 1;
var viewportColumns = this.viewportColumns || 1; var viewportColumns = this.viewportColumns || 1;
var contentId = this.contentId || $("#viewer").parents(".tab-pane.active").attr('id'); var contentId = this.contentId || $('#viewer').parents('.tab-pane.active').attr('id');
if (this.viewportRows && this.viewportColumns) { if (this.viewportRows && this.viewportColumns) {
viewportRows = this.viewportRows || 1; viewportRows = this.viewportRows || 1;
viewportColumns = this.viewportColumns || 1; viewportColumns = this.viewportColumns || 1;
@ -47,13 +50,13 @@ Template.imageViewerViewports.helpers({
// Update viewerData // Update viewerData
ViewerData[contentId].viewportRows = viewportRows; ViewerData[contentId].viewportRows = viewportRows;
ViewerData[contentId].viewportColumns = viewportColumns; ViewerData[contentId].viewportColumns = viewportColumns;
Session.set("ViewerData", ViewerData); Session.set('ViewerData', ViewerData);
this.viewportRows = viewportRows; this.viewportRows = viewportRows;
this.viewportColumns = viewportColumns; this.viewportColumns = viewportColumns;
var numViewports = viewportRows * viewportColumns; var numViewports = viewportRows * viewportColumns;
for (var i=0; i < numViewports; ++i) { for (var i = 0; i < numViewports; ++i) {
var data = { var data = {
viewportIndex: i, viewportIndex: i,
// These two are necessary because otherwise the width and height helpers // These two are necessary because otherwise the width and height helpers
@ -77,14 +80,17 @@ Template.imageViewerViewports.helpers({
ViewerWindows.insert(data); ViewerWindows.insert(data);
} }
// Here we will find out if we need to load any other studies into the viewer // Here we will find out if we need to load any other studies into the viewer
// We will make a list of unique studyInstanceUids // We will make a list of unique studyInstanceUids
var uniqueStudyInstanceUids = []; var uniqueStudyInstanceUids = [];
// Meteor doesn't support Mongo's 'distinct' function, so we have to do this in a loop // Meteor doesn't support Mongo's 'distinct' function, so we have to do this in a loop
ViewerWindows.find().forEach(function(window) { var windows = ViewerWindows.find({}, {
reactive: false
}).fetch();
windows.forEach(function(window) {
var studyInstanceUid = window.studyInstanceUid; var studyInstanceUid = window.studyInstanceUid;
if (!studyInstanceUid) { if (!studyInstanceUid) {
return; return;
@ -99,10 +105,17 @@ Template.imageViewerViewports.helpers({
uniqueStudyInstanceUids.push(studyInstanceUid); uniqueStudyInstanceUids.push(studyInstanceUid);
// If any of the associated studies is not already loaded, load it now // If any of the associated studies is not already loaded, load it now
var loadedStudy = ViewerStudies.findOne({studyInstanceUid: studyInstanceUid}); var loadedStudy = ViewerStudies.findOne({
studyInstanceUid: studyInstanceUid
}, {
reactive: false
});
if (!loadedStudy) { if (!loadedStudy) {
// Load the study // Load the study
Meteor.call('GetStudyMetadata', studyInstanceUid, function(error, study) { getStudyMetadata(studyInstanceUid, function(study) {
log.info("imageViewerViewports GetStudyMetadata: " + studyInstanceUid);
// Sort the study's series and instances by series and instance number // Sort the study's series and instances by series and instance number
sortStudy(study); sortStudy(study);
@ -112,7 +125,9 @@ Template.imageViewerViewports.helpers({
} }
}); });
return ViewerWindows.find(); return ViewerWindows.find({}, {
reactive: false
}).fetch();
} }
}); });
@ -122,9 +137,9 @@ var savedSeriesData,
Template.imageViewerViewports.events({ Template.imageViewerViewports.events({
'CornerstoneMouseDoubleClick .imageViewerViewport': function(e) { 'CornerstoneMouseDoubleClick .imageViewerViewport': function(e) {
var container = $(".viewerMain").get(0); var container = $('.viewerMain').get(0);
var data; var data;
var contentId = this.contentId || $("#viewer").parents(".tab-pane.active").attr('id'); var contentId = this.contentId || $('#viewer').parents('.tab-pane.active').attr('id');
// If there is more than one viewport on screen // If there is more than one viewport on screen
// And one of them is double-clicked, it should be rendered alone // And one of them is double-clicked, it should be rendered alone
@ -183,4 +198,4 @@ Template.imageViewerViewports.events({
$('.imageViewerViewport').eq(0).addClass('zoomed'); $('.imageViewerViewport').eq(0).addClass('zoomed');
} }
} }
}); });

View File

@ -59,7 +59,7 @@ function resultDataToStudyMetadata(resultData) {
*/ */
Services.DIMSE.Instances = function(studyInstanceUid) { Services.DIMSE.Instances = function(studyInstanceUid) {
//var url = buildUrl(server, studyInstanceUid); //var url = buildUrl(server, studyInstanceUid);
var result = DIMSE.retrieveInstances(studyInstanceUid); var result = DIMSE.retrieveInstances(studyInstanceUid, null, {0x00080018 : ""});
console.log("DIMSE Instance retrieval"); console.log("DIMSE Instance retrieval");
console.log(result); console.log(result);

View File

@ -1,5 +1,10 @@
<template name="tabContent"> <template name="tabContent">
<div role="tabpanel" class="tab-pane {{active}}" id="{{contentid}}"> <div role="tabpanel" class="tab-pane {{active}}" id="{{contentid}}">
{{ >loadingText }} {{>loadingText}}
<div class="viewerContainer">
<!-- This extra viewerContainer div only exists to be destroyed when switching tabs.
It is a workaround because Meteor's onDestroyed and destruction handlers don't fire
when removing the actual template from the DOM.-->
</div>
</div> </div>
</template> </template>

View File

@ -1,4 +1,8 @@
.tab-pane .tab-pane
.loadingTextDiv .loadingTextDiv
h5 h5
color: #777 color: #777
.viewerContainer
height: 100%
width: 100%

View File

@ -7,9 +7,9 @@
{{ >tabTitle }} {{ >tabTitle }}
{{ /each }} {{ /each }}
</ul> </ul>
<div class="tab-content"> <div id="worklistTabs" class="tab-content">
<div role="tabpanel" class="tab-pane active" id="worklistTab"> <div role="tabpanel" class="tab-pane active" id="worklistTab">
<div id="worklistContainer" class="container"> <div class="worklistContainer">
{{> worklistResult }} {{> worklistResult }}
</div> </div>
</div> </div>

View File

@ -11,20 +11,13 @@ ViewerData = Session.get('ViewerData') || {};
// Create the WorklistTabs collection // Create the WorklistTabs collection
WorklistTabs = new Meteor.Collection(null); WorklistTabs = new Meteor.Collection(null);
WorklistTabs._debugName = 'WorklistTabs';
// Define the WorklistStudies Collection // Define the WorklistStudies Collection
// This is a client-side only Collection which // This is a client-side only Collection which
// Stores the list of studies in the Worklist // Stores the list of studies in the Worklist
WorklistStudies = new Meteor.Collection(null); WorklistStudies = new Meteor.Collection(null);
WorklistStudies._debugName = 'WorklistStudies';
Template.worklist.onCreated(function() {
var self = this;
if (Worklist.subscriptions) {
Worklist.subscriptions.forEach(function(collectionName) {
self.subscribe(collectionName);
});
}
});
Template.worklist.onRendered(function() { Template.worklist.onRendered(function() {
// If there is a tab set as active in the Session, // If there is a tab set as active in the Session,

View File

@ -9,37 +9,18 @@ body
-ms-user-select: none -ms-user-select: none
user-select: none user-select: none
input.worklist-search
height: 25px
background-color: #888888
width: 100%
#tblStudyList #tblStudyList
tr tr
height: 20px height: 20px
.patient-name-input
width: 100%
.worklist-input
width: 100%
.modality-input
width: 100%
.patientid-input
width: 100%
.study-description-input
width: 100%
#worklistTab #worklistTab
background-color: #202020 background-color: #202020
#worklistContainer .worklistContainer
background-color: #202020 background-color: #202020
margin: 0 auto
color: white color: white
width:90% width: 90%
padding-top: 10px padding-top: 10px
#tabs #tabs
@ -99,7 +80,7 @@ input.worklist-search
.tab-content .tab-content
width: 100% width: 100%
height: calc(100% - 91px) height: calc(100% - 91px)
.tab-pane .tab-pane
width: 100% width: 100%
height: 100% height: 100%

View File

@ -1,5 +1,5 @@
<template name="worklistResult"> <template name="worklistResult">
<table id="tblStudyList" class="table table-striped noselect"> <table id="tblStudyList" class="worklistResult table table-striped noselect">
<thead> <thead>
<tr> <tr>
<th> <th>

View File

@ -115,6 +115,20 @@ function search() {
}); });
} }
Template.worklistResult.onCreated(function() {
console.log('WorklistResult onCreated!');
var self = this;
if (Worklist.subscriptions) {
Worklist.subscriptions.forEach(function(collectionName) {
self.subscribe(collectionName);
});
}
});
Template.worklistResult.onDestroyed(function() {
console.log('WorklistResult onDestroyed!');
});
Template.worklistResult.events({ Template.worklistResult.events({
'keydown input': function(e) { 'keydown input': function(e) {
if (e.which === 13) { // Enter if (e.which === 13) { // Enter

View File

@ -15,8 +15,14 @@ table#tblStudyList
#numImages.worklist-search, #modality.worklist-search #numImages.worklist-search, #modality.worklist-search
visibility: hidden visibility: hidden
input.worklist-search
height: 25px
background-color: #888888
width: 100%
tbody tbody
tr tr
height: 20px
padding: 4px padding: 4px
border: 1px solid #282828 border: 1px solid #282828
background-color: black background-color: black

View File

@ -1,5 +1,6 @@
// Maybe we should use regular Worklist collection? // Maybe we should use regular Worklist collection?
WorklistSelectedStudies = new Meteor.Collection(null); WorklistSelectedStudies = new Meteor.Collection(null);
WorklistSelectedStudies._debugName = 'WorklistSelectedStudies';
function handleShiftClick(studyRow, data) { function handleShiftClick(studyRow, data) {
log.info('shiftKey'); log.info('shiftKey');

View File

@ -12,8 +12,6 @@ var StudyMetaData = {};
* @param failCallback The callback function to be executed when the study retrieval has failed * @param failCallback The callback function to be executed when the study retrieval has failed
*/ */
getStudyMetadata = function(studyInstanceUid, doneCallback, failCallback) { getStudyMetadata = function(studyInstanceUid, doneCallback, failCallback) {
log.info('worklistStudy getStudyMetadata');
// If the StudyMetaData cache already has data related to this // If the StudyMetaData cache already has data related to this
// studyInstanceUid, then we should fire the doneCallback with this data // studyInstanceUid, then we should fire the doneCallback with this data
// and stop here. // and stop here.
@ -26,6 +24,8 @@ getStudyMetadata = function(studyInstanceUid, doneCallback, failCallback) {
// If no study metadata is in the cache variable, we need to retrieve it from // If no study metadata is in the cache variable, we need to retrieve it from
// the server with a call. // the server with a call.
Meteor.call('GetStudyMetadata', studyInstanceUid, function(error, study) { Meteor.call('GetStudyMetadata', studyInstanceUid, function(error, study) {
log.info('worklistStudy getStudyMetadata: ' + studyInstanceUid);
if (error) { if (error) {
log.warn(error); log.warn(error);
failCallback(error); failCallback(error);

View File

@ -8,6 +8,7 @@ switchToTab = function(contentId) {
if (!contentId) { if (!contentId) {
return; return;
} }
log.info('Switching to tab: ' + contentId); log.info('Switching to tab: ' + contentId);
// Use Bootstrap's Tab JavaScript to show the contents of the current tab // Use Bootstrap's Tab JavaScript to show the contents of the current tab
@ -15,7 +16,8 @@ switchToTab = function(contentId) {
$('.tabTitle a[data-target="#' + contentId + '"]').tab('show'); $('.tabTitle a[data-target="#' + contentId + '"]').tab('show');
// Remove any previous Viewers from the DOM // Remove any previous Viewers from the DOM
$('#viewer').remove(); $('.viewerContainer').remove();
$('.worklistContainer').remove();
// Update the 'activeContentId' variable in Session // Update the 'activeContentId' variable in Session
Session.set('activeContentId', contentId); Session.set('activeContentId', contentId);
@ -23,7 +25,16 @@ switchToTab = function(contentId) {
// If we are switching to the Worklist tab, reset any CSS styles // If we are switching to the Worklist tab, reset any CSS styles
// that have been applied to prevent scrolling in the Viewer. // that have been applied to prevent scrolling in the Viewer.
// Then stop here, since nothing needs to be re-rendered. // Then stop here, since nothing needs to be re-rendered.
var container;
if (contentId === 'worklistTab') { if (contentId === 'worklistTab') {
container = $('.tab-content').find('#worklistTab').get(0);
var worklistContainer = document.createElement('div');
worklistContainer.classList.add('worklistContainer');
container.appendChild(worklistContainer);
// Use Blaze to render the WorklistResult Template into the container
Blaze.render(Template.worklistResult, worklistContainer);
document.body.style.overflow = null; document.body.style.overflow = null;
document.body.style.height = null; document.body.style.height = null;
document.body.style.minWidth = null; document.body.style.minWidth = null;
@ -67,10 +78,13 @@ switchToTab = function(contentId) {
} }
// Remove the loading text template that is inside the tab container by default // Remove the loading text template that is inside the tab container by default
var viewerContainer = document.createElement('div');
viewerContainer.classList.add('viewerContainer');
container.innerHTML = ''; container.innerHTML = '';
container.appendChild(viewerContainer);
// Use Blaze to render the Viewer Template into the container // Use Blaze to render the Viewer Template into the container
UI.renderWithData(Template.viewer, data, container); Blaze.renderWithData(Template.viewer, data, viewerContainer);
// Retrieve the DOM element of the viewer // Retrieve the DOM element of the viewer
var imageViewer = $('#viewer'); var imageViewer = $('#viewer');
@ -87,4 +101,4 @@ switchToTab = function(contentId) {
document.body.style.position = 'fixed'; document.body.style.position = 'fixed';
} }
}); });
}; };