reportbro-designer
Version:
Designer to create pdf and excel report layouts. The reports can be generated with reportbro-lib (a Python package) on the server.
883 lines (819 loc) • 37.5 kB
JavaScript
import AddDeleteDocElementCmd from './commands/AddDeleteDocElementCmd';
import Band from './container/Band';
import DocElement from './elements/DocElement';
import * as utils from './utils';
/**
* Area to display all bands and its doc elements.
* Further handles dragging of doc elements.
* @class
*/
export default class Document {
constructor(rootElement, showGrid, rb) {
this.rootElement = rootElement;
this.rb = rb;
this.elPanel = null;
this.elTabPdfLayout = null;
this.elTabPdfPreview = null;
this.elDoc = null;
this.elDocContent = null;
this.elHeader = null;
this.elContent = null;
this.elFooter = null;
this.elSelectionArea = null;
this.elDividerMarginLeft = null;
this.elDividerMarginTop = null;
this.elDividerMarginRight = null;
this.elDividerMarginBottom = null;
this.elDividerHeader = null;
this.elDividerFooter = null;
this.elPdfPreview = null;
this.gridVisible = showGrid;
this.gridSize = 10;
this.zoom = 100; // zoom level in percent
this.zoomLevels = [25, 50, 75, 100, 150, 200, 400];
this.pdfPreviewExists = false;
this.pdfPreviewObjectURL = null;
// moving/resizing of element
this.dragging = false;
this.dragElementType = null;
this.dragType = DocElement.dragType.none;
this.dragObjectId = null;
this.dragContainerId = null;
this.dragLinkedContainers = [];
this.dragCurrentContainerId = null;
this.dragStartX = 0;
this.dragStartY = 0;
this.dragCurrentX = 0;
this.dragCurrentY = 0;
this.dragSnapToGrid = false;
this.dragEnterCount = 0;
// drawing rectangle to select multiple elements
this.selectionAreaStarted = false;
this.selectionAreaStartX = 0;
this.selectionAreaStartY = 0;
}
render() {
this.elPanel = document.getElementById('rbro_document_panel');
this.elPanel.addEventListener('mousedown', (event) => {
if (this.rb.isDocElementSelected() && !event.shiftKey) {
this.rb.deselectAll(true);
}
const offset = utils.getElementOffset(this.elDocContent);
this.startSelectionArea(
this.getCoordWithoutZoom(event.pageX - offset.left),
this.getCoordWithoutZoom(event.pageY - offset.top));
});
const elDocTabs = utils.createElement('div', { id: 'rbro_document_tabs', class: 'rbroDocumentTabs' });
elDocTabs.addEventListener('mousedown', (event) => {
// avoid deselection of doc elements when clicking document tab
event.stopPropagation();
});
this.elTabPdfLayout = utils.createElement(
'div', { id: 'rbro_document_tab_pdf_layout', class: 'rbroDocumentTab rbroButton rbroTabButton' },
this.rb.getLabel('documentTabPdfLayout'));
this.elTabPdfLayout.addEventListener('click', (event) => {
this.setDocumentTab(Document.tab.pdfLayout);
});
elDocTabs.append(this.elTabPdfLayout);
this.elTabPdfPreview = utils.createElement(
'div', {
id: 'rbro_document_tab_pdf_preview',
class: 'rbroDocumentTab rbroButton rbroTabButton rbroHidden rbroPdfPreview' +
(this.rb.getProperty('enableSpreadsheet') ? ' rbroXlsxDownload' : '')
},
this.rb.getLabel('documentTabPdfPreview'));
this.elTabPdfPreview.addEventListener('click', (event) => {
this.setDocumentTab(Document.tab.pdfPreview);
});
if (this.rb.getProperty('enableSpreadsheet')) {
const elButtonXlsxDownload = utils.createElement(
'span', {
class: 'rbroIcon-xlsx rbroXlsxDownloadButton',
title: this.rb.getLabel('documentTabXlsxDownload')
});
elButtonXlsxDownload.addEventListener('click', (event) => {
this.rb.downloadSpreadsheet();
});
this.elTabPdfPreview.append(elButtonXlsxDownload);
}
const elClosePdfPreview = utils.createElement(
'span', {
class: 'rbroIcon-cancel',
title: this.rb.getLabel('documentTabClose')
});
elClosePdfPreview.addEventListener('click', (event) => {
this.closePdfPreviewTab();
});
this.elTabPdfPreview.append(elClosePdfPreview);
elDocTabs.append(this.elTabPdfPreview);
this.elPanel.append(elDocTabs);
const docProperties = this.rb.getDocumentProperties();
this.elDoc = utils.createElement(
'div', { id: 'rbro_document_pdf', class: 'rbroDocument rbroDragTarget rbroHidden' });
this.elDocContent = utils.createElement(
'div', {
id: 'rbro_document_content',
class: 'rbroDocumentContent' + (this.gridVisible ? ' rbroDocumentGrid' : '')
});
this.elHeader = utils.createElement(
'div', {
id: 'rbro_header',
class: 'rbroDocumentBand rbroElementContainer',
style: 'top: 0px; left: 0px;'
});
this.elHeader.append(
utils.createElement('div', { class: 'rbroDocumentBandDescription' }, this.rb.getLabel('bandHeader')));
this.elDocContent.append(this.elHeader);
this.elContent = utils.createElement(
'div', {
id: 'rbro_content',
class: 'rbroDocumentBand rbroElementContainer'
});
this.elContent.append(
utils.createElement('div', { class: 'rbroDocumentBandDescription' }, this.rb.getLabel('bandContent')));
this.elDocContent.append(this.elContent);
this.elFooter = utils.createElement(
'div', {
id: 'rbro_footer',
class: 'rbroDocumentBand rbroElementContainer',
style: 'bottom: 0px; left 0px;'
});
this.elFooter.append(
utils.createElement('div', { class: 'rbroDocumentBandDescription' }, this.rb.getLabel('bandFooter')));
this.elDocContent.append(this.elFooter);
this.elDoc.append(this.elDocContent);
this.elSelectionArea = utils.createElement(
'div', { id: 'rbro_selection_area', class: 'rbroHidden rbroSelectionArea' });
this.elDocContent.append(this.elSelectionArea);
this.initializeEventHandlers();
this.elDividerMarginLeft = utils.createElement(
'div', { id: 'rbro_divider_margin_left', class: 'rbroDivider rbroDividerMarginLeft' });
this.elDoc.append(this.elDividerMarginLeft);
this.elDividerMarginTop = utils.createElement(
'div', { id: 'rbro_divider_margin_top', class: 'rbroDivider rbroDividerMarginTop' });
this.elDoc.append(this.elDividerMarginTop);
this.elDividerMarginRight = utils.createElement(
'div', { id: 'rbro_divider_margin_right', class: 'rbroDivider rbroDividerMarginRight' });
this.elDoc.append(this.elDividerMarginRight);
this.elDividerMarginBottom = utils.createElement(
'div', { id: 'rbro_divider_margin_bottom', class: 'rbroDivider rbroDividerMarginBottom' });
this.elDoc.append(this.elDividerMarginBottom);
this.elDividerHeader = utils.createElement(
'div', { id: 'rbro_divider_header', class: 'rbroDivider rbroDividerHeader' });
this.elDoc.append(this.elDividerHeader);
this.elDividerFooter = utils.createElement(
'div', { id: 'rbro_divider_footer', class: 'rbroDivider rbroDividerFooter' });
this.elDoc.append(this.elDividerFooter);
this.elPanel.append(this.elDoc);
this.elPdfPreview = utils.createElement(
'div', { id: 'rbro_document_pdf_preview', class: 'rbroDocumentPreview' });
this.elPanel.append(this.elPdfPreview);
const size = docProperties.getPageSize();
this.updatePageSize(size.width, size.height);
this.updateHeader();
this.updateFooter();
this.updatePageMargins();
this.updateDocumentTabs();
this.setDocumentTab(Document.tab.pdfLayout);
}
initializeEventHandlers() {
this.elDocContent.addEventListener('dragover', (event) => {
this.processDragover(event);
});
this.elDocContent.addEventListener('dragenter', (event) => {
if (this.rb.isBrowserDragActive('docElement')) {
this.dragEnterCount++;
event.preventDefault(); // needed for IE
}
});
this.elDocContent.addEventListener('dragleave', (event) => {
if (this.rb.isBrowserDragActive('docElement')) {
this.dragEnterCount--;
if (this.dragEnterCount === 0) {
const elContainers = document.querySelectorAll('.rbroElementContainer');
for (const elContainer of elContainers) {
elContainer.classList.remove('rbroElementDragOver');
}
this.dragContainerId = null;
}
}
});
this.elDocContent.addEventListener('drop', (event) => {
this.processDrop(event);
return false;
});
}
processMouseMove(event) {
if (this.dragging) {
this.processDrag(event);
} else if (this.selectionAreaStarted) {
const offset = utils.getElementOffset(this.elDocContent);
const area = this.getSelectionArea(
this.getCoordWithoutZoom(event.pageX - offset.left),
this.getCoordWithoutZoom(event.pageY - offset.top));
this.elSelectionArea.style.left = this.rb.toPixel(area.left);
this.elSelectionArea.style.top = this.rb.toPixel(area.top);
this.elSelectionArea.style.width = this.rb.toPixel(area.width);
this.elSelectionArea.style.height = this.rb.toPixel(area.height);
if (this.elSelectionArea.classList.contains('rbroHidden')) {
// show element after css properties are set
this.elSelectionArea.classList.remove('rbroHidden');
}
}
}
/**
* Process dragover event, i.e. new element is being dragged over a valid drop target.
* @param {DragEvent} event
*/
processDragover(event) {
if (this.rb.isBrowserDragActive('docElement')) {
const absPos = utils.getEventAbsPos(event);
let container = null;
if (absPos !== null) {
container = this.getContainer(absPos.x, absPos.y, this.dragElementType, []);
this.dragCurrentX = absPos.x;
this.dragCurrentY = absPos.y;
}
const containerId = (container !== null) ? container.getId() : null;
if (containerId !== this.dragContainerId) {
const elContainers = document.querySelectorAll('.rbroElementContainer');
for (const elContainer of elContainers) {
elContainer.classList.remove('rbroElementDragOver');
}
if (container !== null) {
container.dragOver();
}
this.dragContainerId = containerId;
}
// without preventDefault for dragover event, the drop event is not fired
event.preventDefault();
event.stopPropagation();
}
}
/**
* Process drop event, i.e. new element is dropped on a valid drop target.
* @param {DragEvent} event
*/
processDrop(event) {
if (this.rb.isBrowserDragActive('docElement')) {
event.preventDefault();
const elContainers = document.querySelectorAll('.rbroElementContainer');
for (const elContainer of elContainers) {
elContainer.classList.remove('rbroElementDragOver');
}
if (this.dragElementType === DocElement.type.image && this.rb.getProperty('imageLimit') !== null) {
let imageCount = 0;
const docElements = this.rb.getDocElements(true);
for (const docElement of docElements) {
if (docElement.getElementType() === DocElement.type.image) {
imageCount++;
}
}
if (imageCount >= this.rb.getProperty('imageLimit')) {
alert(this.rb.getLabel('docElementImageCountExceeded').replace(
'${count}', this.rb.getProperty('imageLimit')));
return;
}
}
const absPos = utils.getEventAbsPos(event);
if (absPos !== null) {
this.dragCurrentX = absPos.x;
this.dragCurrentY = absPos.y;
}
let container = this.getContainer(
this.dragCurrentX, this.dragCurrentY, this.dragElementType, []);
while (container !== null && !container.isElementAllowed(this.dragElementType)) {
container = container.getParent();
}
if (container !== null && container.isElementAllowed(this.dragElementType)) {
const offset = utils.getElementOffset(this.elDocContent);
let x = this.getCoordWithoutZoom(this.dragCurrentX - offset.left);
let y = this.getCoordWithoutZoom(this.dragCurrentY - offset.top);
let containerOffset = container.getOffset();
x -= containerOffset.x;
y -= containerOffset.y;
if (!event.ctrlKey && this.rb.getDocument().isGridVisible()) {
let gridSize = this.rb.getDocument().getGridSize();
x = utils.roundValueToInterval(x, gridSize);
y = utils.roundValueToInterval(y, gridSize);
}
const initialData = { x: '' + x, y: '' + y, containerId: container.getId() };
const cmd = new AddDeleteDocElementCmd(
true, this.dragElementType, initialData, this.rb.getUniqueId(), container.getId(), -1, this.rb);
this.rb.executeCommand(cmd);
}
}
}
/**
* Process dragging existing element, i.e. element is selected and moved with pressed mouse button (or by touch).
* @param {MouseEvent|TouchEvent} event
*/
processDrag(event) {
const absPos = utils.getEventAbsPos(event);
if (this.dragType === DocElement.dragType.element) {
const container = this.getContainer(absPos.x, absPos.y, this.dragElementType, this.dragLinkedContainers);
const containerId = (container !== null) ? container.getId() : null;
if (containerId !== this.dragCurrentContainerId) {
const elContainers = document.querySelectorAll('.rbroElementContainer');
for (const elContainer of elContainers) {
elContainer.classList.remove('rbroElementDragOver');
}
if (container !== null && containerId !== this.dragContainerId) {
container.dragOver();
}
}
this.dragCurrentContainerId = containerId;
}
this.dragCurrentX = absPos.x;
this.dragCurrentY = absPos.y;
this.dragSnapToGrid = !event.ctrlKey;
const dragObject = this.rb.getDataObject(this.dragObjectId);
if (dragObject !== null) {
const dragDiff = dragObject.getDragDiff(
this.getCoordWithoutZoom(absPos.x - this.dragStartX),
this.getCoordWithoutZoom(absPos.y - this.dragStartY), this.dragType,
(this.dragSnapToGrid && this.isGridVisible()) ? this.getGridSize() : 0);
this.rb.updateSelectionDrag(dragDiff.x, dragDiff.y, this.dragType, null, false);
}
}
updatePageSize(width, height) {
this.elDoc.style.width = this.rb.toPixel(width);
this.elDoc.style.height = this.rb.toPixel(height);
}
updatePageMargins() {
const docProperties = this.rb.getDocumentProperties();
const marginLeft = utils.convertInputToNumber(docProperties.getValue('marginLeft'));
const marginTop = utils.convertInputToNumber(docProperties.getValue('marginTop'));
const marginRight = utils.convertInputToNumber(docProperties.getValue('marginRight'));
const marginBottom = utils.convertInputToNumber(docProperties.getValue('marginBottom'));
const left = this.rb.toPixel(marginLeft);
const top = this.rb.toPixel(marginTop - 1);
const right = this.rb.toPixel(marginRight);
const bottom = this.rb.toPixel(marginBottom);
this.elDividerMarginLeft.style.left = left;
this.elDividerMarginTop.style.top = top;
// hide divider in case margin is 0, otherwise divider is still visible
if (marginLeft !== 0) {
this.elDividerMarginLeft.style.left = left;
this.elDividerMarginLeft.style.display = 'block';
} else {
this.elDividerMarginLeft.style.display = 'none';
}
if (marginTop !== 0) {
this.elDividerMarginTop.style.top = top;
this.elDividerMarginTop.style.display = 'block';
} else {
this.elDividerMarginTop.style.display = 'none';
}
if (marginRight !== 0) {
this.elDividerMarginRight.style.right = right;
this.elDividerMarginRight.style.display = 'block';
} else {
this.elDividerMarginRight.style.display = 'none';
}
if (marginBottom !== 0) {
this.elDividerMarginBottom.style.bottom = bottom;
this.elDividerMarginBottom.style.display = 'block';
} else {
this.elDividerMarginBottom.style.display = 'none';
}
this.elDocContent.style.left = left;
this.elDocContent.style.top = top;
this.elDocContent.style.right = right;
this.elDocContent.style.bottom = bottom;
}
updateHeader() {
const docProperties = this.rb.getDocumentProperties();
if (docProperties.getValue('header')) {
const headerSize = this.rb.toPixel(docProperties.getValue('headerSize'));
this.elHeader.style.height = headerSize;
this.elHeader.style.display = 'block';
this.elDividerHeader.style.top = this.rb.toPixel(
utils.convertInputToNumber(docProperties.getValue('marginTop')) +
utils.convertInputToNumber(docProperties.getValue('headerSize')) - 1);
this.elDividerHeader.style.display = 'block';
this.elContent.style.top = headerSize;
} else {
this.elHeader.style.display = 'none';
this.elDividerHeader.style.display = 'none';
this.elContent.style.top = this.rb.toPixel(0);
}
}
updateFooter() {
const docProperties = this.rb.getDocumentProperties();
if (docProperties.getValue('footer')) {
const footerSize = this.rb.toPixel(docProperties.getValue('footerSize'));
this.elFooter.style.height = footerSize;
this.elFooter.style.display = 'block';
this.elDividerFooter.style.bottom = this.rb.toPixel(
utils.convertInputToNumber(docProperties.getValue('marginBottom')) +
utils.convertInputToNumber(docProperties.getValue('footerSize')));
this.elDividerFooter.style.display = 'block';
this.elContent.style.bottom = footerSize;
} else {
this.elFooter.style.display = 'none';
this.elDividerFooter.style.display = 'none';
this.elContent.style.bottom = this.rb.toPixel(0);
}
}
setDocumentTab(tab) {
const elTabs = document.querySelectorAll('#rbro_document_tabs .rbroDocumentTab');
const elMenuButtons = document.querySelectorAll('.rbroElementButtons .rbroMenuButton');
const elActionButtons = document.querySelectorAll('.rbroActionButtons .rbroActionButton');
for (const elTab of elTabs) {
elTab.classList.remove('rbroActive');
}
// use z-index to show pdf preview instead of show/hide of div because otherwise pdf
// is reloaded (and generated) again
if (tab === Document.tab.pdfLayout) {
this.elTabPdfLayout.classList.add('rbroActive');
this.elDoc.classList.remove('rbroHidden');
this.elPdfPreview.style.zIndex = '';
this.elPdfPreview.style.height = '0';
for (const elMenuButton of elMenuButtons) {
elMenuButton.classList.remove('rbroDisabled');
elMenuButton.draggable = true;
}
for (const elActionButton of elActionButtons) {
elActionButton.disabled = false;
}
} else if (this.pdfPreviewExists && tab === Document.tab.pdfPreview) {
this.elTabPdfPreview.classList.add('rbroActive');
this.elDoc.classList.add('rbroHidden');
this.elPdfPreview.style.zIndex = '1';
this.elPdfPreview.style.height = '';
for (const elMenuButton of elMenuButtons) {
elMenuButton.classList.add('rbroDisabled');
elMenuButton.draggable = false;
}
for (const elActionButton of elActionButtons) {
elActionButton.disabled = true;
}
}
}
openPdfPreviewTab(reportUrl, headers) {
utils.emptyElement(this.elPdfPreview);
if (this.pdfPreviewObjectURL) {
// release resource of previous object data url
URL.revokeObjectURL(this.pdfPreviewObjectURL);
this.pdfPreviewObjectURL = null;
}
if (headers && Object.keys(headers).length > 0) {
// use ajax request so we can set custom headers
// we do not use this solution per default because it is not possible to set a filename
// for the preview pdf (which is displayed in Chrome and used for downloading the pdf), e.g.
// https://stackoverflow.com/questions/53548182/can-i-set-the-filename-of-a-pdf-object-displayed-in-chrome
const self = this;
const xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
const obj = document.createElement('object');
obj.type = 'application/pdf';
obj.width = '100%';
obj.height = '100%';
self.pdfPreviewObjectURL = URL.createObjectURL(xhr.response);
obj.data = self.pdfPreviewObjectURL;
self.elPdfPreview.append(obj);
} else {
alert('preview failed');
}
}
};
xhr.open('GET', reportUrl, true);
for (const headerName in headers) {
if (headers.hasOwnProperty(headerName)) {
xhr.setRequestHeader(headerName, headers[headerName]);
}
}
xhr.send();
} else {
// easy way (no custom headers), set data url for pdf object tag
const pdfObj = utils.createElement(
'object', { data: reportUrl, type: 'application/pdf', width: '100%', height: '100%' })
this.elPdfPreview.append(pdfObj);
}
this.pdfPreviewExists = true;
this.setDocumentTab(Document.tab.pdfPreview);
this.updateDocumentTabs();
}
closePdfPreviewTab() {
this.pdfPreviewExists = false;
utils.emptyElement(this.elPdfPreview);
this.setDocumentTab(Document.tab.pdfLayout);
this.updateDocumentTabs();
}
updateDocumentTabs() {
let tabCount = 1;
if (this.pdfPreviewExists) {
this.elTabPdfPreview.classList.remove('rbroHidden');
tabCount++;
} else {
this.elTabPdfPreview.classList.add('rbroHidden');
}
if (tabCount > 1) {
document.getElementById('rbro_document_tabs').style.display = 'block';
this.elPanel.classList.add('rbroHasTabs');
} else {
document.getElementById('rbro_document_tabs').style.display = 'none';
this.elPanel.classList.remove('rbroHasTabs');
}
}
/**
* Returns container for given absolute position.
* @param {Number} absPosX - absolute x position.
* @param {Number} absPosY - absolute y position.
* @param {String} elementType - needed for finding container, not all elements are allowed
* in all containers (e.g. a frame cannot contain another frame).
* @param {Container[]} ignoreContainers - these containers (and its children) cannot be returned,
* this is useful when we move a container element (e.g. section or frame) and do not want to
* get a container of this element.
* @returns {?Container} Container or null in case no container was found for given position.
*/
getContainer(absPosX, absPosY, elementType, ignoreContainers) {
const offset = utils.getElementOffset(this.elDocContent);
return this.rb.getContainer(
this.getCoordWithoutZoom(absPosX - offset.left),
this.getCoordWithoutZoom(absPosY - offset.top), elementType, ignoreContainers);
}
/**
* Returns scroll y position of document content.
* @returns {Number} scroll y position.
*/
getContentScrollPosY() {
const contentOffset = utils.getElementOffset(this.elDocContent);
const panelOffset = utils.getElementOffset(this.elPanel);
return panelOffset.top - contentOffset.top;
}
isGridVisible() {
return this.gridVisible;
}
toggleGrid() {
this.gridVisible = !this.gridVisible;
if (this.gridVisible) {
this.elDocContent.classList.add('rbroDocumentGrid');
} else {
this.elDocContent.classList.remove('rbroDocumentGrid');
}
}
zoomIn() {
for (let i=0; i < this.zoomLevels.length - 1; i++) {
if (this.zoom === this.zoomLevels[i]) {
this.updateZoomLevel(this.zoomLevels[i + 1]);
break;
}
}
}
zoomOut() {
for (let i=1; i < this.zoomLevels.length; i++) {
if (this.zoom === this.zoomLevels[i]) {
this.updateZoomLevel(this.zoomLevels[i - 1]);
break;
}
}
}
isZoomInPossible() {
return this.zoom < this.zoomLevels[this.zoomLevels.length - 1];
}
isZoomOutPossible() {
return this.zoom > this.zoomLevels[0];
}
/**
* Is called when the page size was changed.
* Updates document style properties and is necessary in case the document is zoomed.
*/
pageSizeChanged() {
this.updateZoomLevel(this.zoom);
}
updateZoomLevel(zoom) {
this.zoom = zoom;
const size = this.rb.getDocumentProperties().getPageSize();
const scaledWidth = size.width * (zoom / 100);
const scaledHeight = size.height * (zoom / 100);
const rbWidth = this.rb.getWidth();
const docPanelWidth = rbWidth - this.rb.getMainPanel().getTotalPanelWidth();
const computedStyle = getComputedStyle(this.elPanel);
const paddingTop = parseInt(computedStyle.paddingTop) || 0;
const paddingBottom = parseInt(computedStyle.paddingBottom) || 0;
const docPanelHeight = this.elPanel.clientHeight - paddingTop - paddingBottom;
let translateX = 0;
if (zoom !== 100) {
if (size.width > docPanelWidth) {
// if there is not enough space in the document panel initially and we zoom out we keep the content
// in default (top left) position and move it to the center manually
this.elDoc.style.transformOrigin = '';
if ((zoom < 100) && (scaledWidth < docPanelWidth)) {
translateX = Math.round(((docPanelWidth - scaledWidth) / 2));
}
} else if (scaledWidth > docPanelWidth) {
// if there is not enough space in the document panel with zoom level applied
// we remove any margin and apply the default transformation (top left)
this.elDoc.style.margin = '0';
this.elDoc.style.transformOrigin = '';
} else {
// if there is enough space in the document panel we use the default margin (auto)
// and apply the transformation from top center
// so the content is automatically centered in the available horizontal space
this.elDoc.style.margin = '';
this.elDoc.style.transformOrigin = 'top center';
}
this.elDoc.style.transform = `translateX(${translateX}px) scale(${this.zoom / 100})`;
} else {
// use default values if no zoom is applied
this.elDoc.style.margin = '';
this.elDoc.style.transform = '';
this.elDoc.style.transformOrigin = '';
}
document.getElementById('rbro_menu_zoom_level').textContent = zoom + ' %';
this.rb.getMenuPanel().updateZoomButtons(this.isZoomInPossible(), this.isZoomOutPossible());
// if there is enough space in the document panel don't show scrollbar
if (scaledWidth < docPanelWidth) {
this.elPanel.style.overflowX = 'hidden';
} else {
this.elPanel.style.overflowX = '';
}
if (scaledHeight < docPanelHeight) {
this.elPanel.style.overflowY = 'hidden';
} else {
this.elPanel.style.overflowY = '';
}
}
getCoordWithoutZoom(coord) {
return Math.round(coord * (100 / this.zoom));
}
getGridSize() {
return this.gridSize;
}
getHeight() {
return this.elDocContent.clientHeight;
}
getElement(band) {
if (band === Band.bandType.header) {
return this.elHeader;
} else if (band === Band.bandType.content) {
return this.elContent;
} else if (band === Band.bandType.footer) {
return this.elFooter;
}
return null;
}
/**
* Return element for page background independent of page margins.
* @return {Element}
*/
getPageElement() {
return this.elDoc;
}
isDragging() {
return this.dragging;
}
isDragged() {
return this.dragging && ((this.dragStartX !== this.dragCurrentX) || (this.dragStartY !== this.dragCurrentY));
}
startDrag(x, y, objectId, containerId, elementType, dragType) {
this.dragging = true;
this.dragStartX = this.dragCurrentX = x;
this.dragStartY = this.dragCurrentY = y;
this.dragElementType = elementType;
this.dragType = dragType;
this.dragObjectId = objectId;
this.dragContainerId = containerId;
this.dragLinkedContainers = [];
this.dragCurrentContainerId = null;
this.dragSnapToGrid = false;
const dragObject = this.rb.getDataObject(this.dragObjectId);
if (dragObject) {
this.dragLinkedContainers = dragObject.getLinkedContainers();
}
}
/**
* Stop dragging existing element, i.e. element was selected and moved, pressed mouse button (or touch)
* was released.
*/
stopDrag() {
const diffX = this.getCoordWithoutZoom(this.dragCurrentX - this.dragStartX);
const diffY = this.getCoordWithoutZoom(this.dragCurrentY - this.dragStartY);
const dragObject = this.rb.getDataObject(this.dragObjectId);
if (dragObject !== null && (diffX !== 0 || diffY !== 0)) {
let container = null;
if (this.dragType === DocElement.dragType.element) {
container = this.rb.getDataObject(this.dragCurrentContainerId);
}
// do not allow to change container of elements when multiple elements from different containers
// are dragged together as this could lead to unexpected results for the user
let selectedObjects = this.rb.getSelectedObjects();
if (selectedObjects.length > 1 && container !== null) {
const firstSelectedObjContainerId = selectedObjects[0].getContainerId();
for (let i=1; i < selectedObjects.length; i++) {
if (selectedObjects[i].getContainerId() !== firstSelectedObjContainerId) {
container = null;
break;
}
}
}
let dragDiff = dragObject.getDragDiff(
diffX, diffY, this.dragType, (this.dragSnapToGrid && this.isGridVisible()) ? this.getGridSize() : 0);
this.rb.updateSelectionDrag(dragDiff.x, dragDiff.y, this.dragType, container, true);
} else {
this.rb.updateSelectionDrag(0, 0, this.dragType, null, false);
}
this.dragging = false;
this.dragType = DocElement.dragType.none;
this.dragObjectId = null;
this.dragContainerId = null;
this.dragCurrentContainerId = null;
const elContainers = document.querySelectorAll('.rbroElementContainer');
for (const elContainer of elContainers) {
elContainer.classList.remove('rbroElementDragOver');
}
}
startBrowserDrag(dragElementType) {
this.dragEnterCount = 0;
this.dragObjectId = null;
this.dragContainerId = null;
this.dragLinkedContainers = [];
this.dragElementType = dragElementType;
this.dragStartX = 0;
this.dragStartY = 0;
this.dragCurrentX = 0;
this.dragCurrentY = 0;
}
startSelectionArea(x, y) {
this.selectionAreaStarted = true;
this.selectionAreaStartX = x;
this.selectionAreaStartY = y;
}
stopSelectionArea(x, y, clearSelection) {
const area = this.getSelectionArea(x, y);
if (area.width > 10 && area.height > 10) {
let docElements = this.rb.getDocElements(true);
for (let docElement of docElements) {
// do not select table text, band elements and containers
if (docElement.isAreaSelectionAllowed()) {
let pos = docElement.getAbsolutePosition();
if (area.left < (pos.x + docElement.getValue('widthVal')) &&
(area.left + area.width) >= pos.x &&
area.top < (pos.y + docElement.getValue('heightVal')) &&
(area.top + area.height) >= pos.y) {
let allowSelect = true;
// do not allow selection of element if its container is already selected,
// e.g. text inside selected frame element
if (docElement.getContainerId()) {
const container = docElement.getContainer();
if (container !== null && container.isSelected()) {
allowSelect = false;
}
}
if (allowSelect) {
this.rb.selectObject(docElement.getId(), clearSelection);
clearSelection = false;
}
}
}
}
}
this.selectionAreaStarted = false;
this.selectionAreaStartX = 0;
this.selectionAreaStartY = 0;
this.elSelectionArea.classList.add('rbroHidden');
}
getSelectionArea(x, y) {
const area = {};
if (x > this.selectionAreaStartX) {
area.left = this.selectionAreaStartX;
area.width = x - this.selectionAreaStartX;
} else {
area.left = x;
area.width = this.selectionAreaStartX - x;
}
if (y > this.selectionAreaStartY) {
area.top = this.selectionAreaStartY;
area.height = y - this.selectionAreaStartY;
} else {
area.top = y;
area.height = this.selectionAreaStartY - y;
}
return area;
}
mouseUp(event) {
if (this.isDragging()) {
this.stopDrag();
}
if (this.selectionAreaStarted) {
const offset = utils.getElementOffset(this.elDocContent);
this.stopSelectionArea(
this.getCoordWithoutZoom(event.pageX - offset.left),
this.getCoordWithoutZoom(event.pageY - offset.top),
!event.shiftKey);
}
}
windowResized() {
// the document content position must be updated in case the available space changed
this.updateZoomLevel(this.zoom);
}
}
Document.tab = {
pdfLayout: 'pdfLayout',
pdfPreview: 'pdfPreview'
};