"use strict";
/* global LoadingView */
/* global MenubarActions */
/* global SACK */
/* global XRegExp */
/* global addPromptToAjax */
/* global addPromptToObject */
/* global amIOnline */
/* global applyFormFieldFunction */
/* global dataAsJson */
/* global dataHasJSONError */
/* global debug */
/* global dropDownMessage */
/* global errorPacketHandler */
/* global escape */
/* global getCacheKey */
/* global getId */
/* global getPageCount */
/* global getPromptsField */
/* global getTranslation */
/* global handleErrorPage */
/* global keepServerCacheAlive */
/* global keylistener */
/* global loglevel */
/* global menubar */
/* global menubarLoading */
/* global popupHandler */
/* global preg_replace_callback */
/* global promptHandling */
/* global setPromptsField */
/* global tabbedPanel */
/* global timezoneFromInternationalizationAPI */
/** Global variable to maintain the cache over the report render session */
var vgen = null;
var i18n = {};
/**
* Add additional translations.
* @param translations the new translations to add
*/
/* jshint -W098 */
function addI18n( translations ) {
/* jshint +W098 */
Object.keys( translations ).forEach(function(key) {
i18n[key] = translations[key];
});
}
/**
* Merge URL parameters into array of existing URL parameters
* The function is intended to prepare the URL parameters that will be send to the server
*
* @param {Array} array Array to merge the new parameters into
* @param {Array} additionalParams Array of parameters to be merged
* @param {boolean} encode If the parameters shoud be encodeUROComponent encoded
* @returns {Array} Array of merged paramters
*/
function prepareAddParameterToArray(array, additionalParams, encode) {
for (var param in additionalParams) {
if (additionalParams[param] === null) {
continue;
}
// Allow additional encoding for special cases like export.
// subreport* is allways encoded and must never be recoded. ever.
// Also check if already encoded.
if (encode && param != 'subreport_ondemand' && param != 'subreport' && !( alreadyEncoded(param) || alreadyEncoded(additionalParams[param]) ) ) {
array[encodeURIComponent(param)] = encodeURIComponent(additionalParams[param]);
} else {
array[ensureParameterPromptKey(param)] = additionalParams[param];
}
}
return array;
}
/**
* Check if a string is already url encoded
* @param input
* @returns true if already encoded
*/
function alreadyEncoded( input ) {
if ( input != null && input.indexOf && input.indexOf('%') == -1 ) {
return false; // Can't be encoded
}
try {
if ( decodeURIComponent( input ) == input ) {
return false; // Gibberish
}
} catch (e) {
// Ok, there is a percent sign but it is not encoded ....
return false;
}
return true;
}
/**
* Remove parameters from the Array.
*
* @param {Array} array Input Array
* @param {Array} additionalParams List of names to be removed from the input array
* @returns {Array} Array with removed entries
*/
/* jshint -W098 */
function prepareRemoveParameterFromArray(array, additionalParams) {
/* jshint +W098 */
for (var param in additionalParams) {
delete(array[param]); // Delete standard param
delete(array[encodeURIComponent(param)]); // delete encoded param of same kind
}
return array;
}
/**
* Prepare additional parameters and encode if needed.
* Will also generte a {@link vgen} if not set already
* Uses {@link prepareaddParameterToArray}
*
* @param {Array} additionalParams parameters to put together
* @param {boolean} encode if it should encode
* @returns {Array} Array of parameters
*/
function prepareAddParameter(additionalParams, encode) {
if (vgen == null) {
vgen = new Date().getTime();
}
// 2018-08-07 - the variables by contain prompts that are not yet encoded. That is why we need to take care.
var preparedVariable = prepareAddParameterToArray({vgen: vgen}, VARIABLES, encode);
return prepareAddParameterToArray(preparedVariable, additionalParams, encode);
}
/**
* Uses {@link prepareAddParameter} to set all variables to the AJAX request
*
* @param {object} ajax the AJAX Request
* @param {Array} additionalParams the parameters
*/
function addParametersToAJAX(ajax, additionalParams) {
var newVars = prepareAddParameter(additionalParams);
for (var param in newVars) {
ajax.setVar( ensureParameterPromptKey( param ), newVars[param]);
}
}
/**
* Ensure that the default zoom has a reasonable value
* @oaram {object} the inout value
* @returns a curated zoom value
*/
function ensureDefaultZoom(zoom) {
switch (zoom) {
case getTranslation("menuBar.zoom.pageWidth"):
case getTranslation("menuBar.zoom.pageHeight"):
case getTranslation("menuBar.zoom.pageFit"):
return zoom;
}
var match = (zoom + '').match(new RegExp("^([0-9]+|([0-9]+)%*)$", "i"));
if (!match || !match[1]) {
return 100;
}
return parseInt(match[2] ? match[2] : match[1]) || 100; // Reset zoom, if not able to parse
}
/**
* Prepares the parameter statement to be send to the server.
* It will put all the additionParams together, add the PROMT parameters
* and return a string of them
*
* @param {Array} additionalParams the parameters
* @param {boolean} [keepPrevious=false] if previously set parameters should be kept
* @returns {string} the combined list of additionalParams and PROMPTS
*/
function buildParameterString(additionalParams, keepPrevious) {
var newVars = prepareAddParameter(additionalParams);
// do not encode here or it will be duplicate. See #28290
// If encode is not set, do encode - or bad things happen.
// seriously: when the prompt parameters are not encoded,
// the can contain '#' which will be interpreted as anchor later on.
// We not want.
if ( addPromptToObject ) { addPromptToObject(newVars, keepPrevious); }
var returnString = [];
// Iterate over the keys of the object and store them in an array
var keys = Object.keys(newVars);
// Sort the keys alphabetically
keys.sort();
// Iterate over the sorted keys and push the corresponding key-value pairs into the returnString array
for (var i = 0; i < keys.length; i++) {
var param = keys[i];
returnString.push(ensureParameterPromptKey(param) + '=' + newVars[param]);
}
return returnString.join("&");
}
/**
* Prompts for subreports may contain paramters that need special treatment.
* @param name a parameter name with a potential #-sign
* @returns if the parameter name included a #-sign, encoded vbalue
*/
function ensureParameterPromptKey( name ) {
return name.indexOf('#') >= 0 ? encodeURIComponent( name ) : name;
}
/**
* Build the page URL.
* @param {string} [page=''] the page to open, can be number or string if it is a subreport
* @param {boolean} isPOST send via POST?
* @param {object} additionalParams list of additional parameters
* @param {boolean} [keepPrevious=false] if previously set parameters should be kept
* @returns {string} The full URL to request a report file
*/
function buildBASEURLForPageFile(page, isPOST, additionalParams, keepPrevious) {
// add a page to the parameters
if (typeof additionalParams == 'undefined') {
additionalParams = {};
}
page = page || '';
// If a page is set, check for its parameters, because it could be a subreport I think
if (page.length > 0 && page.indexOf('?') >= 0) {
// var additionalFolder = '';
var params = page.substr(page.indexOf('?')).parseUri().queryKey;
additionalParams = prepareAddParameterToArray(additionalParams, params);
page = page.substr(0, page.indexOf('?'));
}
// If still set, set.
if (page.length > 0) {
additionalParams.page = page;
}
// 2015-02-16: Add a / only if it is not already there and we want to add a page.
return BASE + (!amIOnline.isOnline ? (BASE[BASE.length - 1] == '/' ? '' : '/') + page : '') + (isPOST === true ? '' : '?' + buildParameterString(additionalParams, keepPrevious));
}
/**
* Page Caching machanism
* @class
*/
var pageCache = function () {
var self = this;
this.cachedPages = {};
this.menu = new MenubarActions();
this.notAllowedParameters = ['vgen'];
this.notAllowedCacheParameterToSave = this.notAllowedParameters.concat(['cmd']);
/**
* Return a cache key for a given pageAJAX
*
* @param {object} pageAJAX the current ajax request for a page
* @param {object} filterArray an optional array of parameters not to take into account
* @returns {string} cache key for the current page
*/
this.getCacheKey = function (pageAJAX, filterArray) {
if (!filterArray) { filterArray = self.notAllowedCacheParameterToSave; }
var key = pageAJAX.requestFile + '?' + pageAJAX.parameterString(function (value, index, originalArray) {
return !filterArray.in_array(value);
});
try {
if (loglevel) {
var debugOutput;
switch (getCacheKey.caller) {
case this.getPage:
debugOutput = "getPage";
break;
case this.setPage:
debugOutput = "setPage";
break;
case this.isPageLoading:
debugOutput = "isPageLoading";
break;
case this.setPageLoading:
debugOutput = "setPageLoading";
break;
}
debug("Cache Key for " + debugOutput + "(): '" + key);
}
} catch (e) {}
return key;
};
/**
* Return a cached page if it has already been loaded
*
* @param {object} pageAJAX the prepared ajax request of the page
* @returns {object} the cache object of the page
*/
this.getPage = function (pageAJAX) {
if (!this.isPageLoading(pageAJAX)) {
return this.cachedPages[this.getCacheKey(pageAJAX, this.notAllowedParameters)];
}
};
/**
* Set a loaded page for a given ajax request
*
* @param {object} pageAJAX the prepared ajax request for the page
* @param {object} page the page (full HTML) of the request
* @returns {object} the page again
*/
this.setPage = function (pageAJAX, page) {
this.cachedPages[this.getCacheKey(pageAJAX)] = page;
return page;
};
/**
* Sets the status of a page to 'loading'
*
* @param {object} pageAJAX the prepared ajax request for the page
*/
this.setPageLoading = function (pageAJAX) {
this.cachedPages[this.getCacheKey(pageAJAX)] = 'loading';
};
/**
* checks if a page status uns 'loading'
*
* @param {object} pageAJAX the prepared ajax request for the page
* @returns {boolean} true if loading
*/
this.isPageLoading = function (pageAJAX) {
return this.cachedPages[this.getCacheKey(pageAJAX)] === 'loading';
};
/**
* Create a new page object that can be set as a cache object
*
* @param {string} pageNr the number of the page or subreport page
* @param {string} body the full, rendered body of the page
* @param {string} pageStyle the full, rendered style of the page
* @returns {object} a page
*/
this.page = function (pageNr, body, pageStyle) {
this.pageBody = body;
this.pageNr = pageNr;
this.css = pageStyle;
this.width = null;
this.height = null;
/**
* Stripped down version of the CSS
* @returns {string} CSS of the page
*/
this.pageStyle = function () {
return this.css.replace(new RegExp('([{}](\r|\n)*?)([^$}@,\r\n]*?\s?({|,)((?!;).)*?)(\r|\n)?', 'gi'), "$1#" + this.pageCurrent() + " $3$6");
};
/**
* The unique ID of the page style, prefixed
* @returns {string} the ID of the page css
*/
this.pageCSSID = function () {
return self.menu.getCurrentPageName(getId('endless-mode').isCurrent() ? this.pageNr : null, '__pageStyles');
};
/**
* returns the page number if it is the current page.
* @returns {string} page number
*/
this.pageCurrent = function () {
return self.menu.getCurrentPageName(getId('endless-mode').isCurrent() ? this.pageNr : null);
};
};
};
var _pageCache = new pageCache();
/**
* PAGE LOADING. Here it will really start a request or return a cached page
*
* @class
* @param {string} pageNr the page number or subreport number to load
* @param {function} onloadEvent a function to call when the report page has finished
* @param {string} fragment an anchor fragment to use
* @param {boolean} dontStartJustNow true if the page request should start loading whan constructed
* @returns {object} page loading object
*/
/* jshint -W098 */
var loadpage = function (pageNr, onloadEvent, fragment, dontStartJustNow) {
/* jshint +W098 */
var self = this;
this.fragment = fragment;
this.pageURL = buildBASEURLForPageFile(pageNr + '.html', true);
this.wasError = false;
this.ajaxCanHandleErrors = true;
this.ajax = new SACK(this.pageURL);
// this.ajax.encodeURIString = true;
this.onLoadEvent = onloadEvent;
this.pageFinished = null;
this.pageNr = pageNr;
this.loadingDone = false;
this.scrollBeforeLoad = {
left: 0,
top: 0
};
this.restartLoadingTimeout = 5; // seconds
this.pageNotFoundErrorHandler = null;
this.pageNotfoundError = {
errorHeader: getTranslation("loadPage.pageNotFound.subject"),
errorMessage: getTranslation("loadPage.pageNotFound.body")
};
this.responseEvaluationError = {
errorHeader: getTranslation("loadPage.responseError.subject"),
errorMessage: getTranslation("loadPage.responseError.body")
};
/**
* Cancel loading of the AJAX request
*/
this.cancelLoading = function () {
try {
if (this.ajax.abort) {
this.ajax.abort();
}
} catch (e) {}
(new LoadingView()).hide();
};
this.ajax.onCompletion = function () {
/* check for error */
var data = self.ajax.response;
var status = self.ajax.responseStatus[0];
delete(VARIABLES.promptonrefresh); // Whatever: this must go.
if (status == 404 && self.pageNr != 1) { // Report is rendered, but page is not available.
return getPageCount.supplyWithData(function () {
(new MenubarActions()).lastPage();
});
} else if (dataHasJSONError(data, self.ajax.xmlhttp)) {
// 2016-10-04 - there is a new indicator for that. It might not reset on errors
menubarLoading.stop('reload');
return;
}
if (status >= 200 && status < 400) {
if (typeof data == "string" && data !== '') {
if (data.match(new RegExp("div class=\"promptdialog\"", "ig"))) {
// Build FULL URL again
var promptDialog = new promptHandling(self.ajax.requestFile + (self.ajax.requestFile.indexOf("?") != -1 ? "&" : "?") + self.ajax.parameterString(null, false), self.onLoadEvent);
promptDialog.popup.show();
_pageCache.setPage(self.ajax, null); // Reset page loading status
return;
} else if (!data.match(new RegExp("<meta name=\"generator\" content=\"i-net Clear Reports\" />"))) {
/*
* new errorPacketHandler({ errorHeader: 'Did not receive
* response as expected', errorMessage: data });
*/
if (status == 200) {
// Received a page, maybe a login form?
var origRequestURL = self.ajax.requestFile;
var origParameters = self.ajax.parameters;
(new popupHandler(origRequestURL + (origRequestURL.indexOf("?") != -1 ? "&" : "?") + self.ajax.parameterString(null, false), function (event, form, input, action) {
self.ajax.requestFile = this.formAction;
self.ajax.createAJAX();
self.ajax.parameters = [];
applyFormFieldFunction(input, function (elem, i) {
self.ajax.setVar( ensureParameterPromptKey(elem.name), elem.value );
});
var onLoad = self.ajax.onCompletion;
self.ajax.onCompletion = function () {
self.ajax.requestFile = origRequestURL;
self.ajax.parameters = origParameters;
if (typeof onLoad == 'function') {
onLoad();
}
};
try {
(new LoadingView()).show();
self.ajax.runAJAX();
} catch (e) {
(new LoadingView()).hide();
debug(e);
} finally {
this.hide();
}
return true;
})).show();
_pageCache.setPage(self.ajax, null); // Reset page loading status
var authHeader = decodeURIComponent((self.ajax.xmlhttp.getResponseHeader('X-Authentication-Message')||'').replace(new RegExp("\\+", "g"), '%20'));
if (authHeader && authHeader != '') {
new dropDownMessage(authHeader);
}
} else {
self.restartLoading();
}
return;
} else {
delete(VARIABLES.cmd);
}
// look for body, remove onload method and create div out of it
var pageStyle = data.replace(new XRegExp('^.*?<style.*?>(.*?)<\/style.*?$', 'sgi'), "$1");
var permissions = dataAsJson(data.replace(new XRegExp('^.*?var permissions=(\{.*?\});.*?$', 'sgi'), "$1"));
self.addStylesAndBodyToDom(pageStyle, data, permissions);
} else {
// Try again
setTimeout(self.restartLoading, self.restartLoadingTimeout * 1000);
}
} else if (!self.ajax.canceled && status > 0) {
if ( keepServerCacheAlive.isReportFinished() ) {
handleErrorPage(data, status);
return;
}
self.restartLoading();
}
};
this.addStylesAndBodyToDom = function (pageStyle, body, permissions) {
var menu = new MenubarActions();
var pageNr = getId('endless-mode').isCurrent() ? self.pageNr : null;
var currentPageName = menu.getCurrentPageName(pageNr);
pageStyle = pageStyle.replace(new RegExp('body', 'ig'), "div.htmlviewer_body");
body = body.replace(new XRegExp('^.*?<body(.*?)class="(.*?)"(.*?)(onload=".*?")?(.*?<\/)body.*?$', 'si'), "<div class='htmlviewer_body $2'$1$3$5div>");
body = preg_replace_callback('/(<[^>]*?)(href|src|action)="(?!data:)([^">]*?)"([^>]*?>)/ig', self.linkReplacer, body);
// Note: this may not work with multiple URLs in the same styl
body = preg_replace_callback('/(<[^>]*?style="[^">]*?:url\\()(?!data:)([^;">]*?)(\\)[;">])/ig', self.inlineCssLinkReplacer, body);
// Now do the stuff!
if (!getId(currentPageName)) {
// This is terrible!!!
alert(getTranslation("loadpage.unexpectedError"));
return;
}
self.setPageContent(_pageCache.setPage(self.ajax, new _pageCache.page(self.pageNr, body, pageStyle)));
menu.updatePermissions(permissions);
};
/**
* Set a page content to the current tab
* @param {object} page the page from the cache
* @returns {boolean} true if successfull
*/
this.setPageContent = function (page) {
if (!page) {
return false;
}
var css = getId(page.pageCSSID());
if (!css) {
css = document.createElement('style');
css.setAttribute('type', 'text/css');
css.setAttribute('media', 'screen,print');
css.id = page.pageCSSID();
document.getElementsByTagName("head")[0].appendChild(css);
}
if (css.styleSheet) { // IE does it this way
css.styleSheet.cssText = page.pageStyle();
} else { // everyone else does it this way
css.innerHTML = ""; // Clear first
css.appendChild(document.createTextNode(page.pageStyle()));
}
getId(page.pageCurrent()).innerHTML = page.pageBody;
getId(page.pageCurrent()).htmlViewerPage = page; // Save page object to current node.
self.finalizeLoadPage(page.pageCurrent(), page.pageCSSID(), page);
};
// Finally reset the Menu-things
this.finalizeLoadPage = function (currentPageName, cssID, page) {
var menu = new MenubarActions();
if (currentPageName == null) {
currentPageName = menu.getCurrentPageName();
}
if (self.pageNotFoundErrorHandler != null) {
self.pageNotFoundErrorHandler.closeFunction();
(new LoadingView()).show();
self.pageNotFoundErrorHandler = null;
}
getId('__contentwrapper').style.display = 'block';
if (typeof self.onLoadEvent == 'function') {
self.onLoadEvent();
}
// after "has been load" stuff, so we do not need to take care in multi-load
var width = null,
height = null;
if (cssID) {
try {
// regex = "@page(.|[\r\n])*?size:(.*?);";
var regex = ".body.*?{[^}]*?width:([0-9]+)px;[^}]*?height:([0-9]+)px;";
// Use IE styles as well.
var matches = (getId(cssID).innerHTML || getId(cssID).styleSheet.cssText).match(new RegExp(regex));
width = matches[1].trim();
height = matches[2].trim();
} catch (e) {}
}
if (page) {
page.width = width;
page.height = height;
}
// async get size that is set in the end - also: load the group tree!
getPageCount.setSizeOfContent();
// set prepared size for the time being.
var content = getId(currentPageName);
if ( width && height ) {
/* content.style.width = width + 'px';
content.style.height = height + 'px'; */
getPageCount.preliminaryDATA = {page:{width: width, height: height}};
getPageCount.updateSizeOfContent( getPageCount.preliminaryDATA );
}
// Font-Autoscaling
var dom = content.nodeName == 'IFRAME' ? (content.contentWindow || content.contentDocument).document : content;
if (dom) {
try {
var elements = [].slice.call(dom.getElementsByTagName('p'));
var advancedHTML = typeof dom.querySelectorAll == 'function' ? [].slice.call(dom.querySelectorAll(".no-scaling p")) : [];
elements = elements.filter( function(e) { return advancedHTML.indexOf( e ) == -1; } );
this.fontAutoScaling(elements);
} catch (e) {
throw e;
}
// Check all Links in the new DOM for subreport and reorder links
var links = dom.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
var link = links[i];
var linkURL = link.href.parseUri();
// Check for subreport
if (!(linkURL.queryKey.subreport_ondemand || linkURL.queryKey.reorder)) {
continue;
}
/*if (linkURL.queryKey.reorder) {
// return;
}*/
link.addEvent('click', function (event) {
KEYBOARD_LISTENER.stopEvent(event);
var element = event.currentTarget || event.srcElement;
var uri = element.href.parseUri();
amIOnline.check(function (isOnline) {
// Only available in online mode.
if (!isOnline) {
return;
}
if (uri.queryKey.subreport_ondemand) {
// This is a subreport on demand, so remove the subreport
// variable, or there will be an exception on the server.
if (uri.queryKey.subreport) {
delete(uri.queryKey.subreport);
}
menu.openSubreport(uri);
} else if (uri.queryKey.reorder) {
var prompts = getPromptsField();
for (var i = 0; i < prompts.length; i++) {
if (prompts[i].name == "reorder") {
prompts.splice(i, 1);
break;
}
}
prompts.push({
"name": "reorder",
"value": uri.queryKey.reorder
});
setPromptsField(prompts);
menu.refreshReport(null);
}
});
return false;
});
}
}
// Has to be done at the one so that the IE9+, Edge gets a repaint event at the proper time
menu.setSafeZoom( $.jStorage.get( 'menu.zoom' , DEFAULTZOOM ) );
self.loadingDone = true;
if (typeof self.pageFinished == 'function') {
self.pageFinished(self);
}
// Ok Done. Hide.
(new LoadingView()).hide();
};
/**
* Convert pt to px. if the entry already had px, nothing will be done.
* @param sizeInPtOrPx size in pt or px
* @retun size in px if it was pt
*/
this.ptToPx = function( sizeInPtOrPx ) {
return sizeInPtOrPx.replace(new RegExp("([0-9]+)pt", "g"), function (match, group0) {
return (parseInt(group0, 10) * 96 / 72) + "px";
});
};
// Autoscaling of p-block-elements
this.fontAutoScaling = function (textBlockElements) {
// Find all div blocks arround the p-Blocks
var divBlocks = [];
for (var i = 0; i < textBlockElements.length; i++) {
let element = textBlockElements[i];
let block = element.parentNode;
if (block.nodeName != 'DIV') {
continue;
}
/* var rotated = block.className.match(new RegExp("rot(90|270)"));
if ( rotated != null ) {
block.removeClassName(rotated[0]);
block.rotationClassName = rotated[0];
} */
// This is problematic for rotated glyphs
element.style.lineHeight = element.elementStyle().lineHeight; // required to force (IE) to have the correct line-height or it is derived from the parent - and thus never changed
element.fontSizeInPx = this.ptToPx( element.elementStyle().fontSize );
element.fontSizeInPx = element.fontSizeInPx.substr(0, element.fontSizeInPx.length - 2);
element.subFontSizeInitialised = false;
if (divBlocks.indexOf(element.parentNode) == -1) {
block.textBlocks = [];
block.contentHeight = 0;
divBlocks.push(element.parentNode);
block.fontSizeInPx = element.fontSizeInPx;
}
element.lineHeight = this.getLineHeight(element);
block.textBlocks.push(element);
block.contentHeight += element.offsetHeight;
// Adjust fontSizeInPx to the largest value for the Block
if (element.fontSizeInPx > block.fontSizeInPx) {
block.fontSizeInPx = element.fontSizeInPx;
}
}
for (var blockNumber = 0; blockNumber < divBlocks.length; blockNumber++) {
let block = divBlocks[blockNumber];
// Check if there is a min-height set. If so we need to convert it
// to a height setting
var minHeight = block.elementStyle().height;
var height = block.elementStyle().minHeight;
minHeight = parseInt(minHeight.substring(0, minHeight.length - 2));
height = parseInt(height.substring(0, height.length - 2));
height = minHeight > height && height > 0 ? height : minHeight;
if (height > 0) {
block.style.height = height + 'px';
}
// there may be lots and lots of Ps in a div - each with a different
// font size.
var boxHeight = block.offsetHeight;
if (boxHeight >= block.contentHeight) {
// Everything is great!
if (block.rotationClassName) {
block.addClassName(block.rotationClassName);
}
// ContentHeight matches boxheight, so we don't need to define it here.
// fixes a small problem where both was equal and the text was cut off then.
// 2014-09-20 - Do not set on auto, but remove it entirely.
delete(block.style.height);
continue;
}
// Set the reference of the font-size in PX
block.style.fontSize = block.fontSizeInPx + 'px';
// block.style.lineHeight = '1em'; // required to force (IE) to have the correct line-height or it is derived from the parent - and thus never changed
var newSizeAdjustment = 0; // Current adjustment from default value
// in %
var adjustmentStepSize = 1 / block.textBlocks.length;
while (boxHeight < block.contentHeight) {
newSizeAdjustment += adjustmentStepSize;
block.contentHeight = 0;
for (var textBlockNumber = 0; textBlockNumber < block.textBlocks.length; textBlockNumber++) {
var textBlock = block.textBlocks[textBlockNumber];
// Initialize the text-sub-elements on every p to have the
// correct font-size
if (!textBlock.subFontSizeInitialised) {
textBlock.subFontSizeInitialised = true;
var node = textBlock.firstChild;
while (node) {
if (node.elementStyle) {
var fontSizeInPx = this.ptToPx( node.elementStyle().fontSize );
fontSizeInPx = fontSizeInPx.substr(0, fontSizeInPx.length - 2);
node.style.fontSize = (fontSizeInPx == textBlock.fontSizeInPx) ? 'inherit' : (100 / textBlock.fontSizeInPx * fontSizeInPx) + '%';
}
node = node.nextSibling;
}
// Set the corrected fontSize in % in relation to the
// surrounding block
textBlock.fontSizeInPercent = (100 / block.fontSizeInPx * textBlock.fontSizeInPx);
}
textBlock.style.fontSize = (textBlock.fontSizeInPercent - newSizeAdjustment) + '%';
block.contentHeight += textBlock.offsetHeight;
}
// Minimum font-size of 50%;
if (newSizeAdjustment > 50) {
break;
}
}
if (block.rotationClassName) {
block.addClassName(block.rotationClassName);
}
}
};
this.lineHeightCache = {};
this.getLineHeight = function (element) {
var family = element.elementStyle().fontFamily;
var size = element.elementStyle().fontSize;
if (typeof this.lineHeightCache[family] == 'undefined') {
// Init font
this.lineHeightCache[family] = {};
}
// Check on Fontsize
if (typeof this.lineHeightCache[family][size] == 'undefined') {
var temp = document.createElement(element.nodeName);
temp.setAttribute("style", "margin:0px;padding:0px;font-family:" + family + ";font-size:" + element.elementStyle().fontSize);
temp.innerHTML = "test";
temp = element.parentNode.appendChild(temp);
this.lineHeightCache[family][size] = temp.clientHeight;
temp.parentNode.removeChild(temp);
}
return this.lineHeightCache[family][size];
};
this.timeoutCallback = function( event ) {
// We need to check if we are still in polling mode.
// If so, lets restart loading the current page.
if ( keepServerCacheAlive.isReportFinished() ) {
return;
}
$.Events.stopEvent( event );
self.restartLoading();
};
// lets try iframe on an error
// This relies on the postMessage function of newer browsers
// and needs: if ( typeof parent != 'undefined' && parent.postMessage) {
// parent.postMessage(document.body.scrollHeight, '*'); }
// on the onload function of the loaded pages body.
this.errorCallback = function () {
if (dataHasJSONError(self.ajax.response)) {
return;
}
// Build Frame
self.scrollBeforeLoad.top = getId('__contentwrapper').scrollTop;
self.scrollBeforeLoad.left = getId('__contentwrapper').scrollLeft;
var menu = new MenubarActions();
var iframe = document.createElement("iframe");
iframe.id = menu.getCurrentPageName(self.pageNr, "__contentIFrame");
iframe.name = iframe.id;
iframe.setAttribute('scrolling', 'no');
iframe.style.display = "none";
var finished = false;
var messageFunction = function (event) {
finished = true;
var data = event.data;
// If this message does not come with what we want, discard it.
if ((typeof data).toLowerCase() == "string" || !data.message) {
new errorPacketHandler(self.responseEvaluationError);
return;
} else if (data.message != 'cssBody' + iframe.name) {
// Not our message?
return;
}
// Lets build the page!
self.addStylesAndBodyToDom(data.css, data.body, data.permissions);
iframe.parentNode.removeChild(iframe);
// Clear the window Event after we are done!
window.removeEvent("message", messageFunction);
};
// load event for the iframe to display the content
iframe.addEvent('load', function () {
// Check If we can send a postMessage
if (iframe.contentWindow.postMessage) {
// Register the Message Event for PostMessage receival
window.addEvent("message", messageFunction);
// Send a message
var message = "getFrameContent" + iframe.name;
iframe.contentWindow.postMessage(message, "*");
} else {
// if not, Replace and hide
getId('__contentwrapper').style.display = "none";
iframe.style.display = "block";
self.finalizeLoadPage(iframe.id);
finished = true;
}
getId('__contentwrapper').scrollTop = self.scrollBeforeLoad.top;
getId('__contentwrapper').scrollLeft = self.scrollBeforeLoad.left;
});
window.setTimeout(function () {
if (!finished) {
self.pageNotFoundErrorHandler = new errorPacketHandler(self.pageNotfoundError);
}
}, TIMEOUTBEFOREERROR);
iframe.src = self.ajax.requestFile + (typeof fragment != 'undefined' ? "#" + fragment : "");
getId('__contentwrapper').parentNode.appendChild(iframe);
};
try {
/* If this goes wrong, ignore it */
this.ajax.onLocalFileFunction = this.errorCallback;
this.ajax.xmlhttp.onerror = this.errorCallback;
this.ajax.xmlhttp.ontimeout = this.timeoutCallback;
} catch (e) {
this.ajaxCanHandleErrors = false;
}
this.linkReplacer = function (matches) {
var schema = matches[2];
var urlpart = matches[3];
var expression = "^((https?|file|ftp|smb|afp):(\/\/)?|(mailto|data):|" + escape(BASE).replace(new RegExp("\\+", "g"), "\\+") + ")";
if (!urlpart.match(new RegExp(expression))) {
// urlpart = buildBASEURLForPageFile(urlpart, false, {}, true); // 2018-08-07 Links should already have encoded parameters, but the prompts are not yet.
// urlpart = buildBASEURLForPageFile(urlpart, false); // 2019-05-13
urlpart = buildBASEURLForPageFile(urlpart, false, {}, true); // 2022-10-07 keep previous parameters here, or e.g. reorder will be wrong
}
if (urlpart.match(new RegExp("^#(.*?)$"))) {
// ScrollToDiv
urlpart += "\" onclick=\"if(!event){var event=window.event;}if(event){event.cancelBubble=true;event.returnValue=false;}if(event&&event.stopPropagation){event.stopPropagation();}if(event&&event.preventDefault){event.preventDefault();}getId('popupviewer_content').scrollTop=getId('" + ((urlpart == "#") ? "popupviewer_content" : urlpart
.substr(1)) + "').offsetTop;return false;";
}
return matches[1] + schema + '="' + urlpart + '"' + matches[4];
};
this.inlineCssLinkReplacer = function (matches) {
var urlpart = matches[2];
var expression = "^((https?|file|ftp|smb|afp):(\/\/)?|(mailto|data):|" + escape(BASE).replace(new RegExp("\\+", "g"), "\\+") + ")";
if (!urlpart.match(new RegExp(expression))) {
urlpart = buildBASEURLForPageFile(urlpart, false); // 2019-05-13
}
return matches[1] + urlpart + matches[3];
};
/**
* Start loading of the current loadpage object
*/
this.startLoading = function (cacheOnly) {
// Set all parameters
addParametersToAJAX(self.ajax, {
page: pageNr + '.html'
});
addPromptToAjax && addPromptToAjax(self.ajax);
// start background thread to keep the cache alive
keepServerCacheAlive.startPolling(); // restart the polling mechanism
var cachedPage = _pageCache.getPage(self.ajax);
if (cachedPage) {
debug("Loading Page #" + pageNr + " from Cache.");
(new LoadingView()).show();
this.setPageContent(cachedPage);
return;
}
if (_pageCache.isPageLoading(self.ajax) || cacheOnly) {
return;
}
// now we have all the stuff for the cache
this.ajax.onLoading = function () {
_pageCache.setPageLoading(self.ajax);
};
try {
(new LoadingView()).show();
debug("Newly Loading Page #" + pageNr + ".");
self.ajax.runAJAX();
} catch (e) {
(new LoadingView()).hide();
// If there was an error and an iframe is not yet being constructed:
// throw up an error
if (self.ajaxCanHandleErrors && getId((new MenubarActions()).getCurrentPageName()).nodeName != 'IFRAME') {
debug("Error Loading Page #" + pageNr + ".");
new errorPacketHandler(this.pageNotfoundError);
} else {
debug("Loading Page #" + pageNr + " with iFrame.");
this.errorCallback();
}
}
};
/**
* Stop loading of the current loadpage object
*/
this.stopLoading = function () {
if (this.loadingDone) {
return;
}
// use the previously defined cancel method if still loading
// it will also hide the loading view
this.cancelLoading();
};
/**
* restart loading of the current loadpage object.
* Will also reset the cache
*/
this.restartLoading = function () {
// Reset the Cache on restart.
_pageCache.setPage(self.ajax, null);
// Show Hide pair so it does not flicker
self.startLoading();
(new LoadingView()).hide();
};
if (!dontStartJustNow) {
self.startLoading();
}
};
/*******************************************************************************
* INITIALIZE
******************************************************************************/
var URI, BASE, PROMPT = ( window.htmlviewer ? window.htmlviewer.PROMPT : PROMPT ) || [],
VARIABLES, KEYBOARD_LISTENER,
/** Timeout of loading an internal frame before an error pops up. */
TIMEOUTBEFOREERROR = 10000,
/** This parameter needs to be reset after the first page request. */
PROMPTONREFRESH,
/** If the report has prompts ... */
HASPROMPTS,
/** DRILLDOWN and SUBREPORTS are only enabled in Online mode and not when the reports parameter is used */
DRILLDOWNANDSUBREPORTSDISABLED = false,
// Viewer Options
HASNOGROUPTREE, HASNOGROUPTREE, HASNOZOOM, HASNOTEXTSEARCH, HASNOPRINTBUTTON, HASNOEXPORTBUTTON, HASNOPROMPTONREFRESH, CANSHOWPERMALINK, DEFAULTZOOM,
GROUPTREEOPEN,
/* Default Named Options for skaling the page: PAGE_FIT, PAGE_WIDTH, PAGE_HEIGHT */
ZOOMINGOPTIONS = [];
/**
* Initializes the htmlViewer.
* @function
*/
var initHTMLViewer = function () {
/**
* Base URL for the content
*/
URI = ((String)(URI || document.location)).parseUri();
// Do not modify the base url
// if (URI.directory.substr(URI.directory.length - 1) == '/') {
// URI.directory = URI.directory.substr(0, URI.directory.length - 1);
// }
PROMPT = ( window.htmlviewer ? window.htmlviewer.PROMPT : PROMPT ) || [],
VARIABLES = Object.extend(URI.queryKey, {}); // 2019-05-13 Do not add the Prompts. They will always be added for the request.
// Prompts using the index instead of the name will otherwise take over and a refresh has no effect.
// See below. The list of prompts will be put into PROMPT and removed from VARIABLES. Otherwise a re-submit of the prompts will always have the prompts from VARIABLES
// This parameter needs to be reset after the first page request.
PROMPTONREFRESH = VARIABLES.promptonrefresh == 'true' || VARIABLES.promptonrefresh == '1';
HASNOPROMPTONREFRESH = VARIABLES.haspromptonrefresh == 'false';
HASPROMPTS = ( PROMPT.length > 0 && !HASNOPROMPTONREFRESH ) || PROMPTONREFRESH;
// PROMPT.length = 0; // Reset Prompt // 2019-05-13 - do not reset. see above
HASNOGROUPTREE = VARIABLES.hasgrouptree == 'false';
GROUPTREEOPEN = VARIABLES.grouptreeopen == 'true';
HASNOZOOM = VARIABLES.haszoomcontrol == 'false';
HASNOTEXTSEARCH = VARIABLES.hastextsearchcontrols == 'false';
HASNOPRINTBUTTON = VARIABLES.hasprintbutton == 'false';
HASNOEXPORTBUTTON = VARIABLES.hasexportbutton == 'false';
CANSHOWPERMALINK = typeof VARIABLES.canshowpermalink == 'undefined' || VARIABLES.canshowpermalink == 'true';
// Prepare Zooming Options
ZOOMINGOPTIONS = Object.freeze({
PAGE_FIT: getTranslation("menuBar.zoom.pageFit"),
PAGE_WIDTH: getTranslation("menuBar.zoom.pageWidth"),
PAGE_HEIGHT: getTranslation("menuBar.zoom.pageHeight")
});
// Prepare Default Zoom
DEFAULTZOOM = ensureDefaultZoom( (isNaN(VARIABLES.defaultzoom) ? ZOOMINGOPTIONS[VARIABLES.defaultzoom] : VARIABLES.defaultzoom) || $.jStorage.get("menu.zoom", getTranslation("menuBar.zoom.pageFit")) );
delete(VARIABLES.canshowpermalink);
delete(VARIABLES.hasgrouptree);
delete(VARIABLES.grouptreeopen);
delete(VARIABLES.haszoomcontrol);
delete(VARIABLES.hastextsearchcontrols);
delete(VARIABLES.defaultzoom);
delete(VARIABLES.init);
// If prompthandling was enabled - remove it now
delete(VARIABLES.promptonrefresh);
// This is the HTML Viewer - lets make that statement!
VARIABLES.export_fmt = 'html';
VARIABLES.viewer = 'html';
if (VARIABLES.reports) {
// Disable drilldown and subreports on demand. They do not work here.
DRILLDOWNANDSUBREPORTSDISABLED = true;
}
if (VARIABLES.title) {
document.title = VARIABLES.title;
}
// Set timezone offset if available
VARIABLES.timezone = timezoneFromInternationalizationAPI();
(function(){
// Move away VARIABLEs starting with 'prompt'.
var VAR_KEYS = Object.keys( VARIABLES );
var TMP_PROMPTS = [];
for( var i=0; i<VAR_KEYS.length; i++ ) {
var key = VAR_KEYS[i];
if ( key.toLowerCase().indexOf( 'prompt' ) === 0 ) {
// the key starts with 'prompt'
TMP_PROMPTS.push({
name: key,
value: VARIABLES[key]
});
delete( VARIABLES[key] );
}
}
if ( PROMPT.length === 0 ) {
PROMPT = TMP_PROMPTS; // only set the new Prompts if they ware empty before.
}
})();
// Globalize
if ( window.htmlviewer ) {
window.htmlviewer.PROMPT = PROMPT;
window.HASPROMPTS = HASPROMPTS;
window.HASNOPROMPTONREFRESH = HASNOPROMPTONREFRESH;
window.htmlviewer.MenubarActions = MenubarActions;
window.htmlviewer.popupHandler = popupHandler;
}
KEYBOARD_LISTENER = KEYBOARD_LISTENER || new keylistener();
KEYBOARD_LISTENER.init();
// Ok, the scripts are there, but the viewer.html is not.
if (!getId('__contentwrapper') || !getId('__menuBarWrapper') || !getId('__grouptreewrapper')) {
return; // don't bother.
}
// The base will need the file if one report or reports variable are set.
// Otherweise it will screw up the URL.
BASE = URI.protocol + '://' + URI.authority + URI.directory + URI.file;
amIOnline.check(function (isOnline) {
if (!isOnline) {
// IF not online, this HAS TO BE the folder where the actual pages are in.
BASE = BASE.substr(0, BASE.lastIndexOf('.'));
}
// Make sure the base does not end with a trailing slash. This will lead to problems
// see #28765. The Slash will be added within the buildBASEURLForPageFile method.
// 2015-02-16 as of now: The trailing / has to stay, e.g. the IIS needs it
// if ( BASE[BASE.length-1] == '/' ) {
// BASE = BASE.substr(0, BASE.length-1);
// }
var menu = new menubar();
(new tabbedPanel()).createTabBar();
(window.htmlviewer || {}).GLOBAL_STARTUP_ERROR === true || menu.menubaractions.firstPage();
});
};
$.Events.addInitEvent(initHTMLViewer);