LT-253: Creating dynamic form components

This commit is contained in:
Bruno Alves de Faria 2016-07-12 15:37:53 -03:00 committed by Erik Ziegler
parent 88f737644f
commit cb875b1913
72 changed files with 1169 additions and 126 deletions

View File

@ -15,6 +15,8 @@ tracker # Meteor's client-side reactive programming library
es5-shim # ECMAScript 5 compatibility for older browsers.
ecmascript # Enable ECMAScript2015+ syntax in app code
ohif:core
insecure # Allow all DB writes from clients (for prototyping)
cornerstone
worklist

View File

@ -90,6 +90,7 @@ natestrauser:select2@4.0.2
npm-bcrypt@0.8.6_3
npm-mongo@1.4.45
observe-sequence@1.0.12
ohif:core@0.0.1
ordered-dict@1.0.8
orthanc-remote@0.0.1
peppelg:bootstrap-3-modal@1.0.4

View File

@ -1,3 +1,6 @@
import { OHIF } from 'meteor/ohif:core';
OHIF.viewer = OHIF.viewer || {};
Template.viewer.onCreated(function() {
// Attach the Window resize listener
$(window).on('resize', handleResize);
@ -7,10 +10,6 @@ Template.viewer.onCreated(function() {
log.info('viewer onCreated');
OHIF = window.OHIF || {
viewer: {}
};
OHIF.viewer.loadIndicatorDelay = 500;
OHIF.viewer.defaultTool = 'wwwc';
OHIF.viewer.refLinesEnabled = true;
@ -57,7 +56,7 @@ Template.viewer.onCreated(function() {
}
var contentId = this.data.contentId;
if (ViewerData[contentId].loadedSeriesData) {
log.info('Reloading previous loadedSeriesData');
@ -66,7 +65,7 @@ Template.viewer.onCreated(function() {
} else {
log.info('Setting default ViewerData');
OHIF.viewer.loadedSeriesData = {};
ViewerData[contentId].loadedSeriesData = OHIF.viewer.loadedSeriesData;
// Update the viewer data object
@ -80,7 +79,7 @@ Template.viewer.onCreated(function() {
// Update the ViewerStudies collection with the loaded studies
ViewerStudies.remove({});
this.data.studies.forEach(function(study) {
study.selected = true;
ViewerStudies.insert(study);

View File

@ -6,7 +6,7 @@ label.radio-option
height: 0
width: 0
span
padding-left: 20px
padding-left: 23px
position: relative
&:before
background: white

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
Template.matchedProtocols.onRendered(function() {
$('#matchedProtocols button').tooltip(OHIF.viewer.tooltipConfig);
});

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
// Define a global variable that will be used to refer to the Protocol Engine
// It must be populated by HP.setEngine when the Viewer is initialized and a ProtocolEngine
// is instantiated on top of the LayoutManager. If the global ProtocolEngine variable remains
@ -214,7 +216,7 @@ HP.ProtocolEngine = class ProtocolEngine {
score: matchedDetails.score,
protocol: protocol
});
}
}
});
if (!matched.length) {
@ -300,7 +302,7 @@ HP.ProtocolEngine = class ProtocolEngine {
if (!protocol.protocolMatchingRules) {
return;
}
var studies = WorklistStudies.find({
patientId: study.patientId,
studyDate: {

View File

@ -4,10 +4,10 @@ SimpleSchema.extendOptions({
allowedSelect2Values: Match.Optional(Array),
});
export const AdditionalFinding = new SimpleSchema({
export const schema = new SimpleSchema({
measurableDisease: {
type: String,
label: 'Measurable Disease',
label: 'Measurable disease',
allowedValues: ['Present', 'Absent'],
defaultValue: 'Absent',
optional: true

View File

@ -7,6 +7,13 @@
</div>
{{>radioOptionGroup (stateDataWithKey "measurableDisease")}}
</div>
<!-- <h4>Testing dynamic components</h4>
{{#form id="testForm" schema=currentSchema}}
{{>inputText key='test' label='testing'}}
{{>groupRadio mixins='schemaData' key='acceptableImageQuality'}}
{{/form}} -->
<div class="row">
<div class="header">
Supplementary Measurements

View File

@ -1,14 +1,12 @@
import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict';
// TODO: Check why I can't use absolute paths for this? Keeps saying module not found
import { AdditionalFinding } from '../../../both/schema/additionalFinding';
import { schema as AdditionalFindingsSchema } from 'meteor/lesiontracker/both/schema/additionalFindings';
Template.additionalFindings.onCreated(function additionalFindingsOnCreated() {
console.log('additionalFindingsOnCreated');
const instance = Template.instance();
instance.currentSchema = AdditionalFinding;
instance.currentSchema = AdditionalFindingsSchema;
instance.state = new ReactiveDict();
if (!instance.data.currentTimepointId) {

View File

@ -25,21 +25,22 @@
.header
height: 33px
line-height: 33px
font-weight: 400
font-weight: bold
font-size: 14px
padding: 0 10px
padding: 1px 13px 0
color: $textPrimaryColor
background-color: $uiGrayDark
background-color: $uiGray
.form-group
padding: 10px
padding: 7px 11px
color: $textPrimaryColor
&:not(:last-child)
border-bottom: $uiBorderThickness solid $uiBorderColorDark
h5
font-weight: 300
font-weight: bold
margin-bottom: 8px
.control-label
font-size: 14px

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
Template.toolbarSection.helpers({
// Returns true if the view shall be split in two viewports
splitView() {

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
Template.toolbarSectionButton.helpers({
activeClass() {
const instance = Template.instance();

View File

@ -1,5 +1,8 @@
import { OHIF } from 'meteor/ohif:core';
import { TimepointApi } from 'meteor/lesiontracker/lib/api/timepoint';
OHIF.viewer = OHIF.viewer || {};
Session.setDefault('activeViewport', false);
Session.setDefault('leftSidebar', null);
Session.setDefault('rightSidebar', null);
@ -22,10 +25,6 @@ Template.viewer.onCreated(() => {
var contentId = instance.data.contentId;
OHIF = OHIF || window.OHIF || {
viewer: {}
};
OHIF.viewer.loadIndicatorDelay = 3000;
OHIF.viewer.defaultTool = 'wwwc';
OHIF.viewer.refLinesEnabled = true;

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
/**
* Activates a set of lesions when lesion table row is clicked
*

View File

@ -35,7 +35,7 @@ Package.onUse(function(api) {
api.addFiles('log.js', [ 'client', 'server' ]);
// Schema
api.addFiles('both/schema/additionalFinding.js', [ 'client', 'server' ]);
api.addFiles('both/schema/additionalFindings.js', [ 'client', 'server' ]);
// Client-side collections
api.addFiles('client/collections/LesionLocations.js', 'client');

View File

@ -0,0 +1,49 @@
import { OHIF } from 'meteor/ohif:core';
/*
* Base component to template instances of all dynamic components
*/
class Component {
// Set up the component
constructor(templateInstance) {
// Store the component in the current view
templateInstance.view._component = this;
// Create an object to register section's content
templateInstance.sections = {};
// Store the template instance in the component
this.templateInstance = templateInstance;
// Store the component's registered sub-components
this.registeredItems = new Set();
}
// Self register the component in its first parent component
registerSelf() {
const parent = OHIF.blaze.getParentComponent(this.templateInstance.view);
if (parent) {
// Store this component's parent in a property
this.parent = parent;
// Add this component in its parent's registered items list
parent.registeredItems.add(this);
}
}
// Self unregister the component in its first parent component
unregisterSelf() {
const parent = OHIF.blaze.getParentComponent(this.templateInstance.view);
if (parent) {
// Remove the parent property from this component
delete this.parent;
// Remove this component from its parent's registered items list
parent.registeredItems.delete(this);
}
}
}
OHIF.Component = Component;

View File

@ -0,0 +1,26 @@
// Core files
import './component.js';
import './mixin.js';
import './template.js';
// Mixins
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/schemaData.js';
import './mixins/select.js';
import './mixins/select2.js';
// Templates
import './templates/custom.html';
import './templates/div.html';
import './templates/form.html';
import './templates/input.html';
import './templates/select.html';
// wrappers
import './wrappers/label.html';
import './wrappers/title.html';

View File

@ -0,0 +1,126 @@
import { OHIF } from 'meteor/ohif:core';
import { _ } from 'meteor/underscore';
// Create an object to store all the application mixins
OHIF.mixins = {};
// Class to manage new mixins and its dependencies
class Mixin {
// Create the mixin instance
constructor({ dependencies, composition }) {
// Store the mixin dependencies
this.dependencies = dependencies || '';
// Store the mixin composition
this.composition = composition;
}
// Initialize the mixin applying all its composition functions
init(template, data, applied, behaviors) {
const dependenciesArray = this.dependencies.split(' ');
_.each(dependenciesArray, dependency => {
// Go to next dependency if the current dependency string is blank
if (!dependency) {
return;
}
// Get the dependent mixin to be initizalized
const mixin = Mixin.getMixin(dependency);
// Initizalize the mixin dependencies recursively
mixin.init(template, data, applied, behaviors);
});
// Apply the mixin's composition behaviors to the template
this.apply(template, data, applied, behaviors);
}
// Add the mixin's composition behaviors to the template
apply(template, data, applied, behaviors) {
// Ignore if the mixin was already applied to the template
if (_.contains(applied, this)) {
return;
}
// Store the mixin's composition
const composition = this.composition;
// Iterate over each behavior
_.each(behaviors, behavior => {
// Execute something only after all the mixins are done
let functionName = behavior;
if (functionName === 'onMixins') {
functionName = 'onRendered';
}
if (behavior === 'onData' && composition[behavior]) {
// If it's just data manipulation, call it immediately
composition[behavior](data);
} else if (composition[behavior]) {
// Register the behavior in the template
template[functionName](composition[behavior]);
}
});
// Set the current mixin's state as applied
applied.push(this);
}
// Initialize all data manipulation mixins
static initData(data) {
// Split the mixins by space
const mixinsArray = data.mixins.split(' ');
_.each(mixinsArray, mixinName => {
// Ignore blank strings
if (!mixinName) {
return;
}
// Get the current mixin
const mixin = Mixin.getMixin(mixinName);
// Initialize the data manipulation composition
mixin.init(null, data, [], ['onData']);
});
}
// Initialize all the template's mixins
static initAll(template, data) {
// Split the mixins by space
const mixinsArray = data.mixins.split(' ');
_.each(mixinsArray, mixinName => {
// Ignore blank strings
if (!mixinName) {
return;
}
// Get the current mixin
const mixin = Mixin.getMixin(mixinName);
// Initialize blaze default compositions
mixin.init(template, data, [], ['onCreated', 'onRendered', 'onDestroyed', 'events', 'helpers']);
// Execute some behaviors after all mixins are applied
mixin.init(template, data, [], ['onMixins']);
});
}
// Get a mixin by name
static getMixin(mixinName) {
// Get the mixin from mixins object
const mixin = OHIF.mixins[mixinName];
// Throw an error if the mixin does not exists
if (!mixin) {
throw new Error(`Mixin ${mixinName} not found.`);
}
// Return the found mixin
return mixin;
}
}
// Store the Mixin class inside the shared OHIF object
OHIF.Mixin = Mixin;

View File

@ -0,0 +1,16 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
/*
* component: create the base structure to aplly specific component mixins
*/
OHIF.mixins.component = new OHIF.Mixin({
composition: {
onCreated() {
const instance = Template.instance();
// Declare a property that will be shared among all dependent mixins
instance.component = new OHIF.Component(this);
}
}
});

View File

@ -0,0 +1,50 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
/*
* form: controls a form and its registered inputs
*/
OHIF.mixins.form = new OHIF.Mixin({
dependencies: 'group',
composition: {
onCreated() {
const instance = Template.instance();
const component = instance.component;
// Define the form's data schema
const schema = instance.data.schema;
component.schema = schema && schema.newContext();
// Check if the form data is valid in its schema
component.validate = () => {
// Assume validation result as true
let result = true;
// Return true if there's no data schema defined
if (!component.schema) {
return result;
}
// Iterate over each registered form item and validate it
component.registeredItems.forEach(child => {
const key = child.templateInstance.data.key;
// Change result to false if any form item is invalid
if (key && !child.validate()) {
result = false;
}
});
// Return the validation result
return result;
};
},
onRendered() {
const instance = Template.instance();
const component = instance.component;
// Set the component main and style elements
component.$style = component.$element = instance.$('form').first();
}
}
});

View File

@ -0,0 +1,154 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
import { _ } from 'meteor/underscore';
import { $ } from 'meteor/jquery';
/*
* formItem: create a generic controller for form items
* It may be used to manage all components that belong to forms
*/
OHIF.mixins.formItem = new OHIF.Mixin({
dependencies: 'component',
composition: {
onCreated() {
const instance = Template.instance();
const component = instance.component;
// Register the component in the parent component
component.registerSelf();
// Declare the component elements that will be manipulated
component.$element = $();
component.$style = $();
component.$wrapper = $();
// Get or set the component's value using jQuery's val method
component.value = value => {
const isGet = _.isUndefined(value);
if (isGet) {
return component.$element.val();
}
component.$element.val(value).trigger('change');
};
// Disable or enable the component
component.disable = isDisable => {
component.$element.prop('disabled', !!isDisable);
};
// Set or unset component's readonly property
component.readonly = isReadonly => {
component.$element.prop('readonly', !!isReadonly);
};
// Show or hide the component
component.show = isShow => {
const method = isShow ? 'show' : 'hide';
component.$wrapper[method]();
};
// Add or remove a state from the component
component.state = (state, flag) => {
component.$wrapper.toggleClass(`state-${state}`, !!flag);
};
// Set the component in error state and display the error message
component.error = errorMessage => {
// Set the component error state
component.state('error', errorMessage);
// Set or remove the error message
if (errorMessage) {
component.$element.trigger('errorin');
component.$wrapper.attr('data-error', errorMessage);
} else {
component.$element.trigger('errorout');
component.$wrapper.removeAttr('data-error', errorMessage);
}
};
// Check if the component value is valid in its form's schema
component.validate = () => {
// Get the component's form
const form = component.parent;
// Get the form's data schema
const schema = form && form.schema;
// Get the current component's key
const key = instance.data.key;
// Return true if validation is not needed
if (!key || !schema || !component.$wrapper.is(':visible')) {
return true;
}
// Create the data document for validation
const document = {
[key]: component.value()
};
// Check if the document validation failed
if (!schema.validateOne(document, key)) {
// Set the component in error state and display the message
component.error(schema.keyErrorMessage(key));
// Return false for validation
return false;
}
// Remove the component error state and message
component.error(false);
// Return true for validation
return true;
};
},
onRendered() {
const instance = Template.instance();
const component = instance.component;
// Set the element to be controlled
component.$element = instance.$(':input:first');
// Set the element to be styled
component.$style = component.$element;
// Set the most outer wrapper element
component.$wrapper = instance.wrapper.$('*').first();
},
onDestroyed() {
const instance = Template.instance();
// Register the component in the parent component
instance.component.unregisterSelf();
},
onMixins() {
const instance = Template.instance();
const component = instance.component;
// Set the component in jQuery data after all mixins are rendered
component.$element.data('component', component);
},
events: {
// TODO: [design] remove log, show error box/hint over the wrapper
errorin(event, instance) {
console.log('ERROR when validating component', instance.component);
},
// TODO: [design] hide error box/hint
errorout(event, instance) {
}
}
}
});

View File

@ -0,0 +1,50 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
import { _ } from 'meteor/underscore';
/*
* group: controls a group and its registered items
*/
OHIF.mixins.group = new OHIF.Mixin({
dependencies: 'formItem',
composition: {
onCreated() {
const instance = Template.instance();
const component = instance.component;
// Get or set the child components values
component.value = value => {
const isGet = _.isUndefined(value);
if (isGet) {
const result = {};
component.registeredItems.forEach(child => {
const key = child.templateInstance.data.key;
if (key) {
result[key] = child.value();
}
});
return result;
}
const groupValue = typeof value === 'object' ? value : {};
component.registeredItems.forEach(child => {
const key = child.templateInstance.data.key;
const childValue = _.isUndefined(groupValue[key]) ? null : groupValue[key];
child.value(childValue);
});
component.$element.trigger('change');
};
// Disable or enable the component
component.disable = isDisable => {
component.registeredItems.forEach(child => child.disable(isDisable));
};
// Set or unset component's readonly property
component.readonly = isReadonly => {
component.registeredItems.forEach(child => child.readonly(isReadonly));
};
}
}
});

View File

@ -0,0 +1,30 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
import { _ } from 'meteor/underscore';
/*
* groupRadio: controls all the radio inputs inside the group
*/
OHIF.mixins.groupRadio = new OHIF.Mixin({
dependencies: 'group',
composition: {
onCreated() {
const instance = Template.instance();
const component = instance.component;
// Get the selected radio's value or select a radio based on value
component.value = value => {
const isGet = _.isUndefined(value);
const $elements = $();
component.registeredItems.forEach(child => $elements.add(child.$element));
if (isGet) {
return $elements.filter(':checked').val();
}
$elements.filter(`[value='${value}']`).prop('checked', true).trigger('change');
};
}
}
});

View File

@ -0,0 +1,18 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
/*
* input: controls a basic input
*/
OHIF.mixins.input = new OHIF.Mixin({
dependencies: 'formItem',
composition: {
onRendered() {
const instance = Template.instance();
const component = instance.component;
// Set the element to be controlled
component.$element = instance.$('input:first');
}
}
});

View File

@ -0,0 +1,46 @@
import { OHIF } from 'meteor/ohif:core';
import { Blaze } from 'meteor/blaze';
import { Template } from 'meteor/templating';
import { _ } from 'meteor/underscore';
/*
* schemaData: change the component data based on its form's schema data
*/
OHIF.mixins.schemaData = new OHIF.Mixin({
dependencies: 'formItem',
composition: {
onData() {
const data = Template.currentData();
const parent = OHIF.blaze.getParentComponent(Blaze.currentView);
// Get the parent component schema
const schema = parent && parent.schema;
// Get the current component's key
const key = data.key;
// Stop here if there's no key or schema defined
if (!key || !schema) {
return;
}
// Get the current schema data using component's key
const currentSchema = schema._schema[key];
// Stop here if there's no schema data for current key
if (!currentSchema) {
return;
}
// Use schema's label if it was not defined
if (!data.label) {
data.label = new ReactiveVar(currentSchema.label);
}
// TODO: [design] convert allowedValues to items and find a way to get key/value pairs
}
}
});

View File

@ -0,0 +1,18 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
/*
* input: controls a basic select
*/
OHIF.mixins.select = new OHIF.Mixin({
dependencies: 'formItem',
composition: {
onRendered() {
const instance = Template.instance();
const component = instance.component;
// Set the element to be controlled
component.$element = instance.$('select:first');
}
}
});

View File

@ -0,0 +1,18 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
/*
* input: controls a select2 component
*/
OHIF.mixins.select2 = new OHIF.Mixin({
dependencies: 'select',
composition: {
onRendered() {
const instance = Template.instance();
const component = instance.component;
// Set the element to be controlled
component.$element = instance.$('select:first');
}
}
});

View File

@ -0,0 +1,106 @@
import { OHIF } from 'meteor/ohif:core';
import { _ } from 'meteor/underscore';
// Create a new custom template for the base component
Template.baseComponent = new Template('baseComponent', () => {});
// Inject some custom behaviors in the view's construction function
Template.baseComponent.constructView = function(contentFunc, elseFunc) {
// Get the data passed to the template
const data = Template.currentData();
// Check the base template. If it's not informed set as the custom base
data.base || (data.base = 'baseCustom');
// Get the base template object
const baseTemplate = Template[data.base];
// Throw an error if the base template does not exists
if (!baseTemplate) {
throw new Error(`Template ${data.base} not found.`);
}
// Declare the template object and name it as base name + 'Component'
const template = OHIF.blaze.cloneTemplate(baseTemplate, data.base + 'Component');
// Extract the render function from the base template
template.renderFunction = baseTemplate.renderFunction;
// Check for the mixins. If it's not informed set the lowest level mixin
const mixins = data.mixins || 'component';
// Init the data manipulation mixins
OHIF.Mixin.initData(data);
// Create and fill a list of wrappers that will enclose the component
const wrappers = [];
if (data.wrappers) {
const wrappersList = data.wrappers.split(' ');
_.each(wrappersList, wrapper => wrapper && wrappers.push(wrapper));
}
// Declare a variable to store the wrapper instances that will be rendered
const wrapperInstances = [];
// Declare the content function to render the component
let contentFunction = () => {
// Create the most inner content function
const innerContentFunction = () => {
// Assign properties to all wrappers after template's creation
template.onCreated(() => {
const instance = Template.instance();
// Assign the template most outer wrapper
instance.wrapper = wrapperInstances[0] || instance;
// Iterate over all wrappers and assign the component to them
wrapperInstances.forEach(wrappeInstance => {
wrappeInstance.component = instance.component;
});
});
// Return the view instance
return template.constructView(contentFunc, elseFunc);
};
// Apply the mixins to the component
OHIF.Mixin.initAll(template, data);
// Return the recursive function for wrappers
return Blaze.With(data, innerContentFunction);
};
let wrapper;
while (wrapper = wrappers.shift()) {
// Get the wrapper template
const wrapperTemplate = Template[wrapper];
// Throw an error if the wrapper template does not exists
if (!wrapperTemplate) {
throw new Error(`Template ${data.base} not found.`);
}
// Clone the wrapper template to avoid assigning duplicated handlers
const currentTemplate = OHIF.blaze.cloneTemplate(wrapperTemplate);
// Store the child content function to render it inside the wrapper
const childContentFunction = contentFunction;
// Create a function that will enable the recursion for wrappers
const enclosingContentFunction = () => {
// Add the current wrapper's instance to the wrapper instances list
currentTemplate.onCreated(() => wrapperInstances.push(Template.instance()));
// Return the wrapper view instance with its child as content
return currentTemplate.constructView(childContentFunction, elseFunc);
};
// Replace the content function enclosing it recursively
contentFunction = () => {
return Blaze.With(data, enclosingContentFunction);
};
}
return contentFunction(contentFunc, elseFunc);
};

View File

@ -0,0 +1,3 @@
<template name="baseCustom">
{{>UI.contentBlock}}
</template>

View File

@ -0,0 +1,9 @@
<template name="baseDiv">
<div
id="{{this.id}}"
class="{{this.class}}"
{{this.tagAttributes}}
>
{{>UI.contentBlock}}
</div>
</template>

View File

@ -0,0 +1,16 @@
<template name="baseForm">
<form
id="{{this.id}}"
class="{{this.class}}"
name="{{this.name}}"
action="{{this.action}}"
method="{{this.method}}"
autocomplete="{{this.autocomplete}}"
enctype="{{this.enctype}}"
novalidate="{{this.novalidate}}"
target="{{this.target}}"
{{this.tagAttributes}}
>
{{>UI.contentBlock}}
</form>
</template>

View File

@ -0,0 +1,11 @@
<template name="baseInput">
<input
type="{{choose this.type 'text'}}"
id="{{this.id}}"
class="{{this.class}}"
name="{{this.name}}"
value="{{reactive this.value}}"
{{this.tagAttributes}}
>
{{>UI.contentBlock}}
</template>

View File

@ -0,0 +1,16 @@
<template name="baseSelect">
<select
id="{{this.id}}"
class="{{this.class}}"
name="{{this.name}}"
{{this.tagAttributes}}
>
{{>UI.contentBlock}}
{{#each item in (reactive this.items)}}
<option
value="{{item.value}}"
selected="{{#if eq item.value (reactive this.value)}}selected{{/if}}"
>{{item.label}}</option>
{{/each}}
</select>
</template>

View File

@ -0,0 +1,11 @@
<template name="wrapperLabel">
<label class="wrapperLabel {{this.labelClass}}">
{{#if this.labelAfter}}
{{>UI.contentBlock}}
{{/if}}
<span class="wrapperLabelText">{{reactive this.label}}</span>
{{#unless this.labelAfter}}
{{>UI.contentBlock}}
{{/unless}}
</label>
</template>

View File

@ -0,0 +1,11 @@
<template name="wrapperTitle">
<div class="wrapperTitle {{this.labelClass}}">
{{#if this.labelAfter}}
{{>UI.contentBlock}}
{{/if}}
<span class="wrapperTitleText">{{reactive this.label}}</span>
{{#unless this.labelAfter}}
{{>UI.contentBlock}}
{{/unless}}
</div>
</template>

View File

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

View File

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

View File

@ -0,0 +1,7 @@
import './form/form.html';
import './form/group.html';
import './input/groupRadio.html';
import './input/radio.html';
import './input/select.html';
import './input/text.html';

View File

@ -0,0 +1,20 @@
<template name="groupRadio">
{{#group (extend this
class=(concat 'form-group ' this.class)
wrappers=(concat ''
this.wrappers
(valueIf reactive this.label 'wrapperTitle ' '')
)
)}}
{{>UI.contentBlock}}
{{#each item in (reactive this.items)}}
{{>inputRadio
class=this.radioClass
name=this.key
value=item.value
label=item.label
checked=(eq item.value (reactive this.value))
}}
{{/each}}
{{/group}}
</template>

View File

@ -0,0 +1,14 @@
<template name="inputRadio">
{{#baseComponent (extend this
base='baseInput'
mixins=(concat 'input ' this.mixins)
labelAfter=(valueIf (isDefined this.labelAfter) this.labelAfter true)
labelClass=(concat 'checkboxLabel' this.labelClass)
wrappers=(concat ''
this.wrappers
(valueIf reactive this.label 'wrapperLabel ' '')
)
)}}
{{>UI.contentBlock}}
{{/baseComponent}}
</template>

View File

@ -0,0 +1,13 @@
<template name="inputSelect">
{{#baseComponent (extend this
base='baseSelect'
class=(concat 'form-control ' this.class)
mixins=(concat 'select ' this.mixins)
wrappers=(concat ''
this.wrappers
(valueIf reactive this.label 'wrapperLabel ' '')
)
)}}
{{>UI.contentBlock}}
{{/baseComponent}}
</template>

View File

@ -0,0 +1,13 @@
<template name="inputText">
{{#baseComponent (extend this
base='baseInput'
class=(concat 'form-control ' this.class)
mixins=(concat 'input ' this.mixins)
wrappers=(concat ''
this.wrappers
(valueIf reactive this.label 'wrapperLabel ' '')
)
)}}
{{>UI.contentBlock}}
{{/baseComponent}}
</template>

View File

@ -0,0 +1,2 @@
import './base';
import './bootstrap';

View File

@ -0,0 +1,26 @@
import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { ReactiveVar } from 'meteor/reactive-var';
/**
* Global Blaze UI helpers to work with Blaze
*/
// Return the current template instance
Template.registerHelper('instance', () => {
return Template.instance();
});
// Return the session value for the given key
Template.registerHelper('session', key => {
return Session.get(key);
});
// Return the value for given parameter regardless if it's reactive or not
Template.registerHelper('reactive', parameter => {
if (parameter instanceof ReactiveVar) {
return parameter.get();
}
return parameter;
});

View File

@ -0,0 +1,59 @@
import { _ } from 'meteor/underscore';
import { Template } from 'meteor/templating';
/**
* Global Blaze UI helpers to manipulate data
*/
// Base extend function to be used by extend and clone helpers
const extend = (...argsArray) => {
// Create the resulting object
const result = argsArray[0] || {};
// Extract the Spacebars kw hash
const kwHash = _.last(argsArray).hash;
// Extract the given objects
const objects = _.initial(argsArray);
// Iterate over the given objects
_.each(objects, current => {
// Stop here if the current argument is not an object
if (typeof current !== 'object') {
return;
}
// Extend the resulting object with the current argument object
_.extend(result, current);
});
// Extend the resulting object with the Spacebars kw hash
_.extend(result, kwHash);
// Return the resulting object
return result;
};
// Extend the first argument object it with the other argument objects
Template.registerHelper('extend', (...argsArray) => {
return extend(...argsArray);
});
// Create a new object and extends it with the argument objects
Template.registerHelper('clone', (...argsArray) => {
const newArgs = argsArray.slice();
newArgs.unshift({});
return extend(...newArgs);
});
// Return the first thrut value in the given arguments
Template.registerHelper('choose', (...argsArray) => {
// Iterate over the given objects
for (let i = 0; i < argsArray.length; i++) {
// Check if the current value is truth
if (!!argsArray[i]) {
// Return the current value
return argsArray[i];
}
}
});

View File

@ -0,0 +1,4 @@
import './blaze';
import './data';
import './logical';
import './string';

View File

@ -1,3 +1,6 @@
import { _ } from 'meteor/underscore';
import { Template } from 'meteor/templating';
/**
* Global Blaze UI helpers to work with logical operations
*/
@ -63,6 +66,15 @@ Template.registerHelper('choose', (...values) => {
return result;
});
// Return the second parameter if the first is true or the third if it's false
Template.registerHelper('valueIf', (condition, valueIfTrue, valueIfFalse) => {
if (condition) {
return valueIfTrue;
}
return valueIfFalse;
});
// Check if the value is different from undefined
Template.registerHelper('isDefined', value => {
return typeof value !== 'undefined';

View File

@ -0,0 +1,16 @@
import { _ } from 'meteor/underscore';
import { Template } from 'meteor/templating';
/**
* Global Blaze UI helpers to work with Strings
*/
// Concatenate the give strings
Template.registerHelper('concat', (...args) => {
const values = _.initial(args, 1);
let result = '';
_.each(values, value => {
result += value || '';
});
return result;
});

View File

@ -0,0 +1,42 @@
import { OHIF } from 'meteor/ohif:core';
OHIF.blaze = {};
// Clone a template and return the clone
OHIF.blaze.cloneTemplate = (template, newName) => {
if (!template){
return;
}
const name = newName || template.viewName;
const clone = new Template(name, template.renderFunction);
clone.inheritsEventsFrom(template);
clone.inheritsHelpersFrom(template);
clone.inheritsHooksFrom(template);
return clone;
};
// Navigate upwards the component and get the parent with the given view name
OHIF.blaze.getParentView = (view, parentViewName) => {
let currentView = view;
while (currentView) {
if (currentView.name === parentViewName) {
break;
}
currentView = currentView.originalParentView || currentView.parentView;
}
return currentView;
};
// Search for the parent component of the given view
OHIF.blaze.getParentComponent = (view) => {
let currentView = view;
while (currentView) {
currentView = currentView.originalParentView || currentView.parentView;
if (currentView && currentView._component) {
return currentView._component;
}
}
};

View File

@ -0,0 +1 @@
import './blaze.js';

View File

@ -0,0 +1,6 @@
export { OHIF } from './ohif.js';
import './lib';
import './components';
import './helpers';
import './ui';

View File

@ -0,0 +1,11 @@
/*
* Defines the base OHIF object
*/
const OHIF = {};
if (Meteor.isDevelopment) {
window.OHIF = OHIF;
}
export { OHIF };

View File

@ -0,0 +1,15 @@
Package.describe({
name: 'ohif:core',
summary: 'OHIF core components, helpers and UI functions',
version: '0.0.1'
});
Package.onUse(function(api) {
api.versionsFrom('1.3.4.1');
api.use('ecmascript');
api.use('standard-app-packages');
api.use('jquery');
api.mainModule('main.js', 'client');
});

View File

@ -0,0 +1,2 @@
import './draggable/draggable.js';
import './resizable/resizable.js';

View File

@ -11,7 +11,7 @@
class="form-control js-form-update">
{{#each option in allowedValues}}
<option value="{{option}}"
selected="{{#if equals selectedOptionValue option}}selected{{/if}}">
selected="{{#if eq selectedOptionValue option}}selected{{/if}}">
{{option}}
</option>
{{/each}}
@ -20,4 +20,4 @@
</div>
{{ /with }}
</div>
</template>
</template>

View File

@ -1,10 +0,0 @@
import { Template } from 'meteor/templating';
/**
* Compares two variables are equal in value
*
* @returns {boolean}
*/
Template.registerHelper('equals', function(a, b) {
return a === b;
});

View File

@ -1,35 +1,34 @@
Package.describe({
name: 'reactive-form-controls',
summary: 'A set of basic form controls that store their state in a ReactiveDict',
version: '0.0.1'
});
Package.onUse(function(api) {
api.versionsFrom('1.3.4.1');
api.use('standard-app-packages');
api.use('ecmascript');
api.use('jquery');
api.use('stylus');
api.use('reactive-dict');
api.use('templating');
api.use('natestrauser:select2@4.0.1', 'client');
api.addFiles('client/helpers/getSchema.js', ['client']);
api.addFiles('client/helpers/isInvalidKey.js', ['client']);
api.addFiles('client/helpers/stateDataWithKey.js', ['client']);
api.addFiles('client/helpers/equals.js', ['client']);
api.addFiles('client/components/helpBlock/helpBlock.html', ['client']);
api.addFiles('client/components/helpBlock/helpBlock.js', ['client']);
api.addFiles('client/components/radioOptionGroup/radioOptionGroup.html', ['client']);
api.addFiles('client/components/radioOptionGroup/radioOptionGroup.js', ['client']);
api.addFiles('client/components/selectInput/selectInput.html', ['client']);
api.addFiles('client/components/selectInput/selectInput.js', ['client']);
api.addFiles('client/components/select2Input/select2Input.html', ['client']);
api.addFiles('client/components/select2Input/select2Input.js', ['client']);
});
Package.describe({
name: 'reactive-form-controls',
summary: 'A set of basic form controls that store their state in a ReactiveDict',
version: '0.0.1'
});
Package.onUse(function(api) {
api.versionsFrom('1.3.4.1');
api.use('standard-app-packages');
api.use('ecmascript');
api.use('jquery');
api.use('stylus');
api.use('reactive-dict');
api.use('templating');
api.use('natestrauser:select2@4.0.1', 'client');
api.addFiles('client/helpers/getSchema.js', ['client']);
api.addFiles('client/helpers/isInvalidKey.js', ['client']);
api.addFiles('client/helpers/stateDataWithKey.js', ['client']);
api.addFiles('client/components/helpBlock/helpBlock.html', ['client']);
api.addFiles('client/components/helpBlock/helpBlock.js', ['client']);
api.addFiles('client/components/radioOptionGroup/radioOptionGroup.html', ['client']);
api.addFiles('client/components/radioOptionGroup/radioOptionGroup.js', ['client']);
api.addFiles('client/components/selectInput/selectInput.html', ['client']);
api.addFiles('client/components/selectInput/selectInput.js', ['client']);
api.addFiles('client/components/select2Input/select2Input.html', ['client']);
api.addFiles('client/components/select2Input/select2Input.js', ['client']);
});

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
toggleCinePlay = function() {
var element = getActiveViewportElement();
var playClipToolData = cornerstoneTools.getToolState(element, 'playClip');

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
Template.gridLayout.helpers({
height: function() {
var rows = this.rows || 1;

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
var allCornerstoneEvents = 'CornerstoneToolsMouseDown CornerstoneToolsMouseDownActivate ' +
'CornerstoneToolsMouseClick CornerstoneToolsMouseDrag CornerstoneToolsMouseUp ' +
'CornerstoneToolsMouseWheel CornerstoneToolsTap CornerstoneToolsTouchPress ' +
@ -115,7 +117,7 @@ function loadSeriesIntoViewport(data, templateData) {
templateData.imageId = imageId;
// Save the current image ID inside the ViewportLoading object.
//
//
// The ViewportLoading object relates the viewport elements with whichever
// image is currently being loaded into them. This is useful so that we can
// place progress (download %) for each image inside the proper viewports.
@ -144,7 +146,7 @@ function loadSeriesIntoViewport(data, templateData) {
}
// Update the enabled element with the image and viewport data
// This is not usually necessary, but we need them stored in case
// This is not usually necessary, but we need them stored in case
// a sopClassUid-specific viewport setting is present.
enabledElement.image = image;
enabledElement.viewport = cornerstone.getDefaultViewport(enabledElement.canvas, image);
@ -564,4 +566,4 @@ Template.imageViewerViewport.events({
var viewportIndex = $('.imageViewerViewport').index(e.currentTarget);
layoutManager.toggleEnlargement(viewportIndex);
}
});
});

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
Meteor.startup(function() {
cornerstoneTools.loadHandlerManager.setStartLoadHandler(startLoadingHandler);
cornerstoneTools.loadHandlerManager.setEndLoadHandler(doneLoadingHandler);
@ -67,4 +69,4 @@ Template.loadingIndicator.helpers({
return percentComplete + '%';
}
}
});
});

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
toggleCinePlay = function(element) {
var viewports = $('.imageViewerViewport');

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
function getDefaultButtonData() {
var buttonData = [];

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
applyWLPreset = function(presetName, element) {
log.info("Applying WL Preset: " + presetName);
var viewport = cornerstone.getViewport(element);

View File

@ -1,6 +1,8 @@
import { OHIF } from 'meteor/ohif:core';
/**
* This function disables reference lines for a specific viewport element.
* It also enables reference lines for all other viewports with the
* It also enables reference lines for all other viewports with the
* class .imageViewerViewport.
*
* @param element {node} DOM Node representing the viewport element
@ -36,4 +38,4 @@ displayReferenceLines = function(element) {
cornerstoneTools.referenceLines.tool.enable(element, OHIF.viewer.updateImageSynchronizer);
});
};
};

View File

@ -1,4 +1,4 @@
OHIF = window.OHIF || {};
import { OHIF } from 'meteor/ohif:core';
Meteor.startup(function() {
@ -31,7 +31,7 @@ Meteor.startup(function() {
WLPresetBone: ['NUMPAD4', '4'],
WLPresetBrain: ['NUMPAD5', '5']
};
// For now
OHIF.viewer.hotkeys = OHIF.viewer.defaultHotkeys;
@ -262,7 +262,7 @@ function bindHotkey(hotkey, task) {
} else {
fn = hotkeyFunctions[task];
// If the function doesn't exist in the
// If the function doesn't exist in the
// hotkey function list, try the viewer-specific function list
if (!fn && OHIF.viewer && OHIF.viewer.functionList) {
fn = OHIF.viewer.functionList[task];

View File

@ -1,38 +0,0 @@
// Return the current template instance
Template.registerHelper('instance', () => {
return Template.instance();
});
// Return the session value for the given key
Template.registerHelper('session', key => {
return Session.get(key);
});
// Create a new object and extends it with the argument objects
Template.registerHelper('extend', (...argsArray) => {
// Create the resulting object
const result = {};
// Extract the Spacebars kw hash
const kwHash = _.last(argsArray).hash;
// Extract the given objects
const objects = _.initial(argsArray);
// Iterate over the given objects
_.each(objects, current => {
// Stop here if the current argument is not an object
if (typeof current !== 'object') {
return;
}
// Extend the resulting object with the current argument object
_.extend(result, current);
});
// Extend the resulting object with the Spacebars kw hash
_.extend(result, kwHash);
// Return the resulting object
return result;
});

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
var activeTool = 'wwwc';
var defaultTool = 'wwwc';

View File

@ -117,9 +117,6 @@ Package.onUse(function(api) {
api.addFiles('lib/toolManager.js', 'client');
api.addFiles('lib/enablePrefetchOnElement.js', 'client');
api.addFiles('lib/displayReferenceLines.js', 'client');
api.addFiles('lib/ui/draggable/draggable.js', 'client');
api.addFiles('lib/ui/resizable/resizable.js', 'client');
api.addFiles('lib/ui/resizable/resizable.styl', 'client');
api.addFiles('lib/toggleDialog.js', 'client');
api.addFiles('lib/setActiveViewport.js', 'client');
api.addFiles('lib/switchToImageByIndex.js', 'client');
@ -168,7 +165,6 @@ Package.onUse(function(api) {
api.export('LayoutManager', 'client');
// Global objects
api.export('OHIF', 'client');
api.export('ClientId', 'client');
// Collections
@ -177,8 +173,6 @@ Package.onUse(function(api) {
// UI Helpers
api.addFiles([
'lib/helpers/formatDA.js',
'lib/helpers/blaze.js',
'lib/helpers/logical.js',
'lib/helpers/formatJSDate.js',
'lib/helpers/jsDateFromNow.js',
'lib/helpers/formatNumberPrecision.js',