PWV-1: Migrating study association modal to the new structure

This commit is contained in:
Bruno Alves de Faria 2016-09-23 11:15:57 -03:00 committed by Erik Ziegler
parent 8e6ac3bf1e
commit 984ef94403
24 changed files with 180 additions and 145 deletions

View File

@ -31,7 +31,6 @@
<div class="tab-pane" id="viewerTab">
</div>
</div>
{{ >associationModal }}
{{ >optionsModal }}
{{ >serverInformationModal }}
{{ >confirmRemoveTimepointAssociation }}

View File

@ -12,7 +12,6 @@ Router.onBeforeAction('loading');
var data = {
additionalTemplates: [
'associationModal',
'optionsModal',
'serverInformationModal',
'confirmRemoveTimepointAssociation',

View File

@ -31,7 +31,6 @@
<div class="tab-pane" id="viewerTab">
</div>
</div>
{{ >associationModal }}
{{ >serverInformationModal }}
{{ >lastLoginModal }}
{{ >studyContextMenu }}

View File

@ -8,14 +8,16 @@ import './section/section.html';
import './section/section.js';
// Mixins
import './mixins/action.js';
import './mixins/button.js';
import './mixins/checkbox.js';
import './mixins/component.js';
import './mixins/form.js';
import './mixins/formItem.js';
import './mixins/group.js';
import './mixins/groupRadio.js';
import './mixins/input.js';
import './mixins/checkbox.js';
import './mixins/link.js';
import './mixins/schemaData.js';
import './mixins/select.js';
import './mixins/select2.js';
@ -27,6 +29,7 @@ import './templates/custom.html';
import './templates/div.html';
import './templates/form.html';
import './templates/input.html';
import './templates/link.html';
import './templates/select.html';
// wrappers

View File

@ -0,0 +1,37 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
/*
* action: controls an element that will trigger some form API's method
*/
OHIF.mixins.action = new OHIF.Mixin({
dependencies: 'formItem',
composition: {
onRendered() {
const instance = Template.instance();
const component = instance.component;
component.$element.addClass('form-action');
},
events: {
'click .form-action'(event, instance) {
const component = instance.component;
// Extract the action and the params
const { action, params } = instance.data;
// Get the current component's API
const api = component.getApi();
// Stop here if no API or action was defined
if (!api || !action || typeof api[action] !== 'function') {
return;
}
// Call the defined action function
api[action].call(this, params);
}
}
}
});

View File

@ -2,7 +2,7 @@ import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
/*
* input: controls a button
* button: controls a button
*/
OHIF.mixins.button = new OHIF.Mixin({
dependencies: 'formItem',
@ -13,26 +13,6 @@ OHIF.mixins.button = new OHIF.Mixin({
// Set the element to be controlled
component.$element = instance.$('button:first');
},
events: {
'click button'(event, instance) {
const component = instance.component;
// Extract the action and the params
const { action, params } = instance.data;
// Get the current component's API
const api = component.getApi();
// Stop here if no API or action was defined
if (!api || !action || typeof api[action] !== 'function') {
return;
}
// Call the defined action function
api[action].call(this, params);
}
}
}
});

View File

@ -55,10 +55,12 @@ OHIF.mixins.form = new OHIF.Mixin({
// Set the component main and style elements
component.$style = component.$element = instance.$('form:first');
// Block page redirecting on submit
component.$element[0].onsubmit = () => false;
},
events: {
submit: event => event.preventDefault(),
'click .validation-error-container a'(event, instance) {
// Get the target key
const targetKey = $(event.currentTarget).attr('data-target');

View File

@ -0,0 +1,26 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
/*
* link: controls a link
*/
OHIF.mixins.link = new OHIF.Mixin({
dependencies: 'formItem',
composition: {
onRendered() {
const instance = Template.instance();
const component = instance.component;
// Set the element to be controlled
component.$element = instance.$('a:first');
},
events: {
'click a'(event, instance) {
if (instance.data.action) {
event.preventDefault();
}
}
}
}
});

View File

@ -0,0 +1,11 @@
<template name="baseLink">
<a
id="{{this.id}}"
class="{{this.class}} {{#if this.disabled}}disabled{{/if}}"
href="{{choose this.href '#'}}"
title="{{this.title}}"
{{this.tagAttributes}}
>
{{>UI.contentBlock}}
</a>
</template>

View File

@ -1,17 +1,19 @@
<template name="dialogForm">
<div id="{{this.id}}" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog {{this.dialogClass}}" role="document">
{{#form class='modal-content' schema=this.schema hideValidationBox=true api=instance.api}}
{{#form class='modal-content' api=instance.api schema=this.schema hideValidationBox=true}}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
{{#button class='close' action='cancel' tagAttributes=(extend this.tagAttributes aria-label='Close')}}
<span aria-hidden="true">&times;</span>
{{/button}}
<h4 class="modal-title">{{this.title}}</h4>
</div>
<div class="modal-body">
{{>UI.contentBlock}}
</div>
<div class="modal-footer">
{{#button class='btn-default' action='cancel'}}Cancel{{/button}}
{{#button class='btn-primary' action='confirm'}}Confirm{{/button}}
{{#button class='btn btn-default' action='cancel'}}{{choose this.cancelLabel 'Cancel'}}{{/button}}
{{#button class='btn btn-primary' action='confirm'}}{{choose this.confirmLabel 'Confirm'}}{{/button}}
</div>
{{/form}}
</div>

View File

@ -36,7 +36,10 @@ Template.dialogForm.onRendered(() => {
// Create the bootstrap modal
const $modal = instance.$('.modal');
$modal.modal();
$modal.modal({
backdrop: 'static',
keyboard: false
});
// Remove the created modal backdrop from DOM after promise is done
const $backdrop = $modal.next('.modal-backdrop');

View File

@ -1,8 +1,7 @@
<template name="button">
{{#baseComponent (extend this
base='baseButton'
class=(concat 'btn ' this.class)
mixins=(concat 'button ' this.mixins)
mixins=(concat 'button action ' this.mixins)
)}}
{{>UI.contentBlock}}
{{/baseComponent}}

View File

@ -0,0 +1,8 @@
<template name="link">
{{#baseComponent (extend this
base='baseLink'
mixins=(concat 'link action ' this.mixins)
)}}
{{>UI.contentBlock}}
{{/baseComponent}}
</template>

View File

@ -6,6 +6,7 @@ import './dialog/login.js';
import './form/button.html';
import './form/form.html';
import './form/group.html';
import './form/link.html';
import './input/checkbox.html';
import './input/hidden.html';

View File

@ -1,19 +1,10 @@
<template name="associationModal">
<div class="modal" id="associationModal" tabindex="-1" role="dialog" aria-labelledby="associationModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="associationModalLabel">Study Association</h4>
</div>
<div class="modal-body">
{{>studyAssociationTable}}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="saveAssociations">Save</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal" id='cancelAssociation'>Cancel</button>
</div>
</div>
</div>
</div>
<template name="dialogStudyAssociation">
{{#dialogForm (extend this
schema=instance.schema
dialogClass='modal-lg'
title='Study Association'
confirmLabel='Save'
)}}
{{>studyAssociationTable}}
{{/dialogForm}}
</template>

View File

@ -1,40 +1,42 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { Blaze } from 'meteor/blaze';
import { Random } from 'meteor/random';
import { moment } from 'meteor/momentjs:moment';
import { OHIF } from 'meteor/ohif:core';
Template.associationModal.events({
'click #saveAssociations': function(e) {
Template.dialogStudyAssociation.onCreated(() => {
const instance = Template.instance();
instance.data.confirmCallback = (formData, resolve) => {
OHIF.log.info('Saving associations');
const Timepoints = StudyList.timepointApi.timepoints;
// Close the modal
const saveButton = $(e.currentTarget);
saveButton.attr('disabled', true);
saveButton.addClass('btn-success').removeClass('btn-primary');
// Find the rows of the study association table
const tableRows = $('#studyAssociationTable table tbody tr');
const $tableRows = $('#studyAssociationTable table tbody tr');
// Create an empty object to group studies into
var studies = {};
const studies = {};
// Loop through each row to parse the data
tableRows.each(function() {
$tableRows.each(function() {
// Get a selector for this row
var row = $(this);
const $row = $(this);
// Check the includeStudy checkbox to see if we should parse this row
var includeStudy = row.find('input.includeStudy[type="checkbox"]').eq(0).prop('checked');
const includeStudy = $row.find('input.includeStudy[type="checkbox"]').eq(0).prop('checked');
if (!includeStudy) {
return;
}
// Find the selected timepoint option for this study
var timepointInput = row.find('input.timepointOption[type="radio"]:checked');
const $timepointInput = $row.find('input.timepointOption[type="radio"]:checked');
// Find the related label and trim it down to actual label (TODO: do this another way)
var timepointType = timepointInput.val();
const timepointType = $timepointInput.val();
// Get the study metaData by checking the row with the template engine Blaze
var data = Blaze.getData(this);
const data = Blaze.getData(this);
// Concatenate the study data to an array, depending on whether is was marked as baseline
// or follow-up
@ -47,27 +49,25 @@ Template.associationModal.events({
Object.keys(studies).forEach(function(timepointType) {
// Get the studies associated with this timepoint
var relatedStudies = studies[timepointType];
const relatedStudies = studies[timepointType];
// Create an array of all the studyInstanceUids for storage in the Timepoint
var studyInstanceUids = relatedStudies.map(function(study) {
const studyInstanceUids = relatedStudies.map(function(study) {
return study.studyInstanceUid;
});
// Create an array of all the studyDates for storage in the Timepoint
var studyDates = relatedStudies.map(function(study) {
return moment(study.studyDate).toDate();
});
let studyDates = relatedStudies.map(study => moment(study.studyDate).toDate());
// Sort the study dates, so we can get a range for these values
studyDates = studyDates.sort();
// HipaaEventType to log changes in collections
var hipaaEventType;
var hipaaEvent;
let hipaaEventType;
let hipaaEvent;
// Check if these studies are already associated with an existing Timepoint
var existingTimepoint;
let existingTimepoint;
if (timepointType === 'baseline') {
// If we're trying to associate them to the Baseline, we don't need to
// check if the studyInstanceUids are already associated with anything else
@ -87,7 +87,7 @@ Template.associationModal.events({
});
}
var timepointId;
let timepointId;
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
@ -121,7 +121,7 @@ Template.associationModal.events({
eventType: hipaaEventType,
userId: Meteor.userId(),
userName: Meteor.user().profile.fullName,
collectionName: "Timepoints",
collectionName: 'Timepoints',
recordId: timepointId,
patientId: relatedStudies[0].patientId,
patientName: relatedStudies[0].patientName
@ -130,18 +130,6 @@ Template.associationModal.events({
StudyList.timepointApi.storeTimepoints();
// Hide the modal
$('#associationModal').modal('hide');
// Reset the save button to its normal state
saveButton.removeClass('btn-success').addClass('btn-primary');
saveButton.attr('disabled', false);
},
'click #cancelAssociation': function() {
// When the modal is closed, we should reset
// the save button to its normal state
var saveButton = $('#saveAssociations');
saveButton.removeClass('btn-success').addClass('btn-primary');
saveButton.attr('disabled', false);
}
resolve();
};
});

View File

@ -1,3 +0,0 @@
#associationModal
.modal-dialog
width: 80%

View File

@ -1,6 +1,5 @@
// Study-Timepoint Association imports
import './associationModal/associationModal.html';
import './associationModal/associationModal.styl';
import './associationModal/associationModal.js';
import './associationModal/studyAssociationTable/studyAssociationTable.html';

View File

@ -1,43 +1,30 @@
<template name="longitudinalStudyListContextMenu">
{{#if uiSettings.studyListFunctionsEnabled}}
<div id="studyContextMenu" class="studyContextMenu noselect"
oncontextmenu='return false;'
unselectable='on'
onselectstart='return false;'>
<ul>
<li>
<a id="viewStudies" type="button"
title="View Studies">
View
</a>
<hr/>
<a id="launchStudyAssociation" type="button"
data-toggle="modal"
data-target="#associationModal"
title="Launch Study Association">
Associate
</a>
<a id="launchRemoveAssociation" type="button"
data-toggle="modal"
data-target="#confirmRemoveTimepointAssociation"
title="Remove Timepoint Association">
Remove Association
</a>
<hr/>
<a id="viewSeriesDetails" type="button"
title="View Series Details">
View Series Details
</a>
<a class="disabled">Anonymize</a>
<a class="disabled">Send</a>
<hr/>
<a id="exportSelectedStudies"
title="Export Selected Studies">
Export
</a>
<a class="disabled">Delete</a>
</li>
</ul>
</div>
<div id="studyContextMenu" class="studyContextMenu noselect"
oncontextmenu='return false;'
unselectable='on'
onselectstart='return false;'>
<ul>
<li>
<a id="viewStudies" type="button" title="View Studies">View</a>
<hr/>
<!-- {{#link action='associate'}}Associate{{/link}} -->
<a id="launchStudyAssociation" title="Launch Study Association">Associate</a>
<a id="launchRemoveAssociation" type="button"
data-toggle="modal"
data-target="#confirmRemoveTimepointAssociation"
title="Remove Timepoint Association">Remove Association</a>
<hr/>
<a id="viewSeriesDetails" type="button"
title="View Series Details">View Series Details</a>
<a class="disabled">Anonymize</a>
<a class="disabled">Send</a>
<hr/>
<a id="exportSelectedStudies"
title="Export Selected Studies">Export</a>
<a class="disabled">Delete</a>
</li>
</ul>
</div>
{{/if}}
</template>

View File

@ -4,8 +4,10 @@ import { OHIF } from 'meteor/ohif:core';
// default StudyListStudy template.
// See https://github.com/aldeed/meteor-template-extension
const defaultTemplate = 'studyContextMenu';
Template.longitudinalStudyListContextMenu.replaces(defaultTemplate);
StudyList.functions['launchStudyAssociation'] = () => OHIF.ui.showFormDialog('dialogStudyAssociation');
StudyList.functions['removeTimepointAssociations'] = removeTimepointAssociations;
StudyList.functions['exportSelectedStudies'] = exportSelectedStudies;
StudyList.functions['viewStudies'] = viewStudies;

View File

@ -13,6 +13,8 @@ Package.onUse(function(api) {
api.use('stylus');
api.use('random');
api.use('momentjs:moment');
api.use('validatejs');
// Schema for Data Models

View File

@ -20,7 +20,7 @@
{{/if}}
{{#if this.serverType.get}}
<div>
{{#button class='btn-primary' action='save'}}Save{{/button}}
{{#button class='btn btn-primary' action='save'}}Save{{/button}}
</div>
{{/if}}
{{/form}}

View File

@ -15,17 +15,17 @@
<td>{{server.type}}</td>
<td class="text-right">
<div class="btn-group" role="group" aria-label="Actions">
{{#button class='btn-sm btn-default' action='use' params=server title='Use this server' disabled=(isActive server)}}
{{#button class='btn btn-sm btn-default' action='use' params=server title='Use this server' disabled=(isActive server)}}
{{#if isActive server}}
<i class="fa fa-check-square-o"></i>
{{else}}
<i class="fa fa-square-o"></i>
{{/if}}
{{/button}}
{{#button class='btn-sm btn-default' action='edit' params=server title='Edit' disabled=(eq server.origin 'json')}}
{{#button class='btn btn-sm btn-default' action='edit' params=server title='Edit' disabled=(eq server.origin 'json')}}
<i class="fa fa-pencil"></i>
{{/button}}
{{#button class='btn-sm btn-danger' action='delete' params=server title='Remove' disabled=(eq server.origin 'json')}}
{{#button class='btn btn-sm btn-danger' action='delete' params=server title='Remove' disabled=(eq server.origin 'json')}}
<i class="fa fa-trash"></i>
{{/button}}
</div>
@ -38,7 +38,7 @@
{{/each}}
</tbody>
</table>
{{#button class='btn-sm btn-default' action='add'}}
{{#button class='btn btn-sm btn-default' action='add'}}
<i class="fa fa-plus"></i> Add a new server
{{/button}}
{{/form}}

View File

@ -3,33 +3,33 @@
{{>inputHidden key='intervalId'}}
<div class="cine-navigation">
<div class="btn-group">
{{#button disabled=(displaySetDisabled false) action='displaySetPrevious' title='Previous display set'}}
{{#button class='btn' disabled=(displaySetDisabled false) action='displaySetPrevious' title='Previous display set'}}
<i class="fa fa-toggle-up"></i>
{{/button}}
{{#button disabled=(displaySetDisabled true) action='displaySetNext' title='Next display set'}}
{{#button class='btn' disabled=(displaySetDisabled true) action='displaySetNext' title='Next display set'}}
<i class="fa fa-toggle-down"></i>
{{/button}}
</div>
</div>
<div class="cine-controls">
<div class="btn-group">
{{#button action='cineFirst' title='Skip to first image' disabled=(buttonDisabled)}}
{{#button class='btn' action='cineFirst' title='Skip to first image' disabled=(buttonDisabled)}}
<i class="fa fa-fast-backward"></i>
{{/button}}
{{#button action='cinePrevious' title='Previous image' disabled=(buttonDisabled)}}
{{#button class='btn' action='cinePrevious' title='Previous image' disabled=(buttonDisabled)}}
<i class="fa fa-step-backward"></i>
{{/button}}
{{#button action='cineToggle' title='Play / Stop' class=(valueIf isPlaying 'active' '') disabled=(buttonDisabled)}}
{{#button class='btn' action='cineToggle' title='Play / Stop' class=(valueIf isPlaying 'active' '') disabled=(buttonDisabled)}}
{{#if isPlaying}}
<i class="fa fa-fw fa-stop"></i>
{{else}}
<i class="fa fa-fw fa-play"></i>
{{/if}}
{{/button}}
{{#button action='cineNext' title='Next image' disabled=(buttonDisabled)}}
{{#button class='btn' action='cineNext' title='Next image' disabled=(buttonDisabled)}}
<i class="fa fa-step-forward"></i>
{{/button}}
{{#button action='cineLast' title='Skip to last image' disabled=(buttonDisabled)}}
{{#button class='btn' action='cineLast' title='Skip to last image' disabled=(buttonDisabled)}}
<i class="fa fa-fast-forward"></i>
{{/button}}
</div>