241 lines
8.8 KiB
JavaScript
241 lines
8.8 KiB
JavaScript
import { $ } from 'meteor/jquery';
|
|
import { _ } from 'meteor/underscore';
|
|
import { OHIF } from 'meteor/ohif:core';
|
|
|
|
// Allow attaching to jQuery selectors
|
|
$.fn.bounded = function(options) {
|
|
_.each(this, element => {
|
|
const boundedInstance = $(element).data('boundedInstance');
|
|
if (options === 'destroy' && boundedInstance) {
|
|
$(element).removeData('boundedInstance');
|
|
boundedInstance.destroy();
|
|
} else {
|
|
if (boundedInstance) {
|
|
boundedInstance.options(options);
|
|
} else {
|
|
$(element).data('boundedInstance', new Bounded(element, options));
|
|
}
|
|
}
|
|
});
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* This class makes an element bounded to other element's borders.
|
|
*/
|
|
class Bounded {
|
|
|
|
// Initialize the instance with the given element and options
|
|
constructor(element, options={}) {
|
|
this.element = element;
|
|
this.$element = $(element);
|
|
this.options(options);
|
|
this.setBoundedFlag(false);
|
|
|
|
// Force to hardware acceleration to move element if browser supports translate property
|
|
this.useTransform = OHIF.ui.styleProperty.check('transform', 'translate(1px, 1px)');
|
|
}
|
|
|
|
// Set or change the instance options
|
|
options(options={}) {
|
|
// Process the given options and store it in the instance
|
|
const { boundingElement, positionElement, dimensionElement, allowResizing } = options;
|
|
this.positionElement = positionElement || this.element;
|
|
this.$positionElement = $(this.positionElement);
|
|
this.dimensionElement = dimensionElement || this.element;
|
|
this.$dimensionElement = $(this.dimensionElement);
|
|
this.boundingElement = boundingElement;
|
|
this.$boundingElement = $(this.boundingElement);
|
|
this.allowResizing = allowResizing;
|
|
|
|
// Check for fixed positioning
|
|
if (this.$positionElement.css('position') === 'fixed') {
|
|
this.boundingElement = window;
|
|
}
|
|
|
|
// Destroy and initialize again the instance
|
|
this.destroy();
|
|
this.init();
|
|
}
|
|
|
|
// Initialize the bounding behaviour
|
|
init() {
|
|
// Create the event handlers
|
|
this.defineEventHandlers();
|
|
|
|
// Attach the created event handlers to the component
|
|
this.attachEventHandlers();
|
|
|
|
// Add the bounded class to the element
|
|
this.$element.addClass('bounded');
|
|
|
|
// Handle the positioning on window resize
|
|
const $window = $(window);
|
|
const windowResizeHandler = () => {
|
|
// Check if the element is still in DOM and remove the handler if it is not
|
|
if (!this.$element.closest(document.documentElement).length) {
|
|
$window.off('resize', windowResizeHandler);
|
|
}
|
|
|
|
this.$element.trigger('spatialChanged');
|
|
};
|
|
|
|
$window.on('resize', windowResizeHandler);
|
|
|
|
// Trigger the bounding check for the first timepoint
|
|
setTimeout(() => this.$element.trigger('spatialChanged'));
|
|
}
|
|
|
|
// Destroy this instance, returning the element to its previous state
|
|
destroy() {
|
|
// Detach the event handlers
|
|
this.detachEventHandlers();
|
|
|
|
// Remove the bounded class from the element
|
|
this.$element.removeClass('bounded');
|
|
}
|
|
|
|
static spatialInfo(positionElement, dimensionElement) {
|
|
// Create the result object
|
|
const result = {};
|
|
|
|
// Check if the element is the window
|
|
if (!dimensionElement || dimensionElement === window) {
|
|
const $window = $(window);
|
|
const width = $window.outerWidth();
|
|
const height = $window.outerHeight();
|
|
return {
|
|
width,
|
|
height,
|
|
x0: 0,
|
|
y0: 0,
|
|
x1: width,
|
|
y1: height
|
|
};
|
|
}
|
|
|
|
// Get the jQuery object for the elements
|
|
const $dimensionElement = $(dimensionElement);
|
|
const $positionElement = $(positionElement);
|
|
|
|
// Get the integer numbers for element's width
|
|
result.width = $dimensionElement.outerWidth();
|
|
|
|
// Get the integer numbers for element's height
|
|
result.height = $dimensionElement.outerHeight();
|
|
|
|
// Get the position property based on the element position CSS attribute
|
|
const elementPosition = $positionElement.css('position');
|
|
const positionProperty = elementPosition === 'fixed' ? 'position' : 'offset';
|
|
|
|
// Get the element's start position
|
|
const position = $positionElement[positionProperty]();
|
|
result.x0 = position.left;
|
|
result.y0 = position.top;
|
|
|
|
// Get the element's end position
|
|
result.x1 = result.x0 + result.width;
|
|
result.y1 = result.y0 + result.height;
|
|
|
|
// Return the result object
|
|
return result;
|
|
}
|
|
|
|
// Define the event handlers for this class
|
|
defineEventHandlers() {
|
|
this.cssPositionHandler = (elementInfo, boundingInfo) => {
|
|
// Fix element's x positioning and width
|
|
if (this.allowResizing && elementInfo.width > boundingInfo.width) {
|
|
this.$dimensionElement.width(boundingInfo.width);
|
|
this.$positionElement.css('left', boundingInfo.x0);
|
|
this.setBoundedFlag(true);
|
|
} else if (elementInfo.x0 < boundingInfo.x0) {
|
|
this.$positionElement.css('left', boundingInfo.x0);
|
|
this.setBoundedFlag(true);
|
|
} else if (elementInfo.x1 > boundingInfo.x1) {
|
|
this.$positionElement.css('left', boundingInfo.x1 - elementInfo.width);
|
|
this.setBoundedFlag(true);
|
|
}
|
|
|
|
// Fix element's y positioning and height
|
|
if (this.allowResizing && elementInfo.height > boundingInfo.height) {
|
|
this.$dimensionElement.height(boundingInfo.height);
|
|
this.$positionElement.css('top', boundingInfo.y0);
|
|
this.setBoundedFlag(true);
|
|
} else if (elementInfo.y0 < boundingInfo.y0) {
|
|
this.$positionElement.css('top', boundingInfo.y0);
|
|
this.setBoundedFlag(true);
|
|
} else if (elementInfo.y1 > boundingInfo.y1) {
|
|
this.$positionElement.css('top', boundingInfo.y1 - elementInfo.height);
|
|
this.setBoundedFlag(true);
|
|
}
|
|
};
|
|
|
|
this.getCSSTranslate = () => {
|
|
const matrixToArray = str => str.match(/(-?[0-9\.]+)/g);
|
|
const transformMatrix = matrixToArray(this.$positionElement.css('transform')) || [];
|
|
return {
|
|
x: parseFloat(transformMatrix[4]) || 0,
|
|
y: parseFloat(transformMatrix[5]) || 0
|
|
};
|
|
};
|
|
|
|
this.cssTransformHandler = (elementInfo, boundingInfo, translate) => {
|
|
if (elementInfo.x1 > boundingInfo.x1) {
|
|
translate.x -= elementInfo.x1 - boundingInfo.x1;
|
|
}
|
|
|
|
if (elementInfo.y1 > boundingInfo.y1) {
|
|
translate.y -= elementInfo.y1 - boundingInfo.y1;
|
|
}
|
|
|
|
if (elementInfo.x0 < boundingInfo.x0) {
|
|
translate.x += boundingInfo.x0 - elementInfo.x0;
|
|
}
|
|
|
|
if (elementInfo.y0 < boundingInfo.y0) {
|
|
translate.y += boundingInfo.y0 - elementInfo.y0;
|
|
}
|
|
|
|
const translation = `translate(${translate.x}px, ${translate.y}px)`;
|
|
OHIF.ui.styleProperty.set(this.positionElement, 'transform', translation);
|
|
};
|
|
|
|
this.spatialChangedHandler = event => {
|
|
// Get the spatial information for element and its bounding element
|
|
const { positionElement, dimensionElement, boundingElement, useTransform } = this;
|
|
const elementInfo = Bounded.spatialInfo(positionElement, dimensionElement);
|
|
const boundingInfo = Bounded.spatialInfo(boundingElement, boundingElement);
|
|
|
|
// Check if CSS positioning or transform will be used
|
|
const translate = this.getCSSTranslate();
|
|
if (useTransform && (translate.x || translate.y)) {
|
|
this.cssTransformHandler(elementInfo, boundingInfo, translate);
|
|
} else {
|
|
this.cssPositionHandler(elementInfo, boundingInfo);
|
|
}
|
|
};
|
|
}
|
|
|
|
// Attach the event handlers to the element in order to bound it
|
|
attachEventHandlers() {
|
|
this.$element.on('spatialChanged', this.spatialChangedHandler);
|
|
this.$boundingElement.on('resize', this.spatialChangedHandler);
|
|
}
|
|
|
|
// Detach the event handlers from the element
|
|
detachEventHandlers() {
|
|
this.$element.off('spatialChanged', this.spatialChangedHandler);
|
|
this.$boundingElement.off('resize', this.spatialChangedHandler);
|
|
}
|
|
|
|
// This is a means to let outside world know that the element in question has been moved
|
|
setBoundedFlag(value) {
|
|
this.$element.data('wasBounded', value);
|
|
}
|
|
|
|
}
|
|
|
|
OHIF.ui.Bounded = Bounded;
|