/*******************************************************************************
* MENU BAR ACTIONS
******************************************************************************/
/* global CANSHOWPERMALINK */
/* global DEFAULTZOOM */
/* global HASNOEXPORTBUTTON */
/* global HASNOPRINTBUTTON */
/* global HASNOTEXTSEARCH */
/* global HASNOZOOM */
/* global HASPROMPTS */
/* global IEVersion */
/* global KEYBOARD_LISTENER */
/* global PROMPTONREFRESH */
/* global VARIABLES */
/* global _pageCache */
/* global amIOnline */
/* global browserPrint */
/* global closeAllPopUps */
/* global debug */
/* global documentExport */
/* global documentSearch */
/* global editableComboBox */
/* global getId */
/* global getPageCount */
/* global getTranslation */
/* global grouptree */
/* global keepServerCacheAlive */
/* global loadpage */
/* global menubarLoading */
/* global pageCache */
/* global popupHandler */
/* global printListener */
/* global scrollToElement */
/* global tabbedPanel */
/* global vgen */
/* global waitForFinalEvent */
/* global windowSize */
var _menubaractions = null;
/**
* The action on the menu bar. This is a singleton.
*
* @class
*/
/* jshint -W098 */
var MenubarActions = function() {
/* jshint +W098 */
// make singleton
if (_menubaractions != null) {
return _menubaractions;
}
_menubaractions = this; // Singleton Instance
var self = this;
this.currentZoom = 100;
this.maxPages = null;
this.enabledFormats = null;
this.currentPageNumber = (function() {
var currentPage = -1;
return {
get : function() {
if (currentPage > (self.maxPages || currentPage)) {
debug("CurrentPage is out of scope (using maxPage): " + currentPage + " > " + self.maxPages);
return self.maxPages;
}
debug("CurrentPage is good: " + currentPage);
return currentPage;
},
set : function(page) {
currentPage = Math.max(0, Math.min(page, self.maxPages || page));
debug("SETTING PAGE NUMBER: " + page);
// debug("TRACE: " + (new Error()).stack);
return currentPage;
}
};
})();
/** Zoom further in */
this.zoomIn = function(e, slow) {
self.zoom((slow ? 0.5 : 1) * (self.currentZoom >= 100 ? self.currentZoom / 4 : 5), e);
};
/** Zoom out */
this.zoomOut = function(e, slow) {
self.zoom((slow ? 0.5 : 1) * (self.currentZoom > 100 ? -self.currentZoom / 5 : -5), e);
};
// To be implemented, where needed
this.updateZoomStatus = function(zoom) {
};
/**
* Returns the page - element which is currently active, e.g. for loading
*
* @param {number} contentWrapperInnerNumber Number of content wrapper to
* generate name for
* @param {string} prefix Name prefix
* @returns {string} name of the page for parameters
*/
this.getCurrentPageName = function(contentWrapperInnerNumber, prefix) {
var name = typeof prefix == 'undefined' ? '__content' : prefix;
name += '-' + (new tabbedPanel()).frontMostTab();
name += '-' + (getId('single-page').isCurrent() ? 1 : (contentWrapperInnerNumber != null ? contentWrapperInnerNumber : Math.max(1, self.currentPageNumber.get())));
return name;
};
/**
* Apply current zoom to inner Pages
*
* @param {object} contentWrapperInner document element of the inner content
* wrapper
* @param {object} content document element of the content
*/
this.applyZoom = function(contentWrapperInner, content) {
if (!content) {
return;
}
var newZoom = (self.currentZoom / 100);
content = content.parentNode;
var newHeight = (content.offsetHeight || content.scrollHeight) * newZoom, newWidth = content.offsetWidth * newZoom;
contentWrapperInner.style.height = newHeight > 0 ? newHeight + 'px' : '';
contentWrapperInner.style.width = newWidth > 0 ? newWidth + 'px' : '';
if (typeof content.style.transform !== 'undefined') {
// In addition the defaults as well
content.style.transform = "scale(" + newZoom + ")";
content.style.transformOrigin = "50% 0%";
} else if (typeof content.style.MozTransform !== 'undefined') {
content.style.MozTransform = "scale(" + newZoom + ")";
content.style.MozTransformOrigin = "50% 0%";
} else if (typeof content.style.webkitTransform !== 'undefined') {
content.style.webkitTransform = "scale(" + newZoom + ")";
content.style.webkitTransformOrigin = "50% 0%";
} else if (typeof content.style.OTransform !== 'undefined') {
content.style.OTransform = "scale(" + newZoom + ")";
content.style.OTransformOrigin = "50% 0%";
} else if (typeof content.style.msTransform !== 'undefined') {
content.style.msTransform = "scale(" + newZoom + ")";
content.style.msTransformOrigin = "50% 0%";
} else {
content.style.zoom = newZoom * 100 + '%';
}
content.style.marginLeft = Math.round(-(1 - newZoom) / 2 * content.offsetWidth) + "px";
};
/**
* Calculates the optimal width of the current tab so that the page is fully
* visible
*
* @returns {number} width in pixel
*/
this.optimalWidth = function() {
// Determine the width from the outer Wrapper. The wrapper will be
// changed by the group tree if set.
var contentWrapper = getId('__contentwrapper');
if (contentWrapper.offsetWidth === 0) {
contentWrapper = getId('__menuBarWrapper'); // For the time being. Usually the init phase
}
var outerWidth = contentWrapper.offsetWidth;
if (!getId('__grouptreewrapper').isCurrent()) {
// If hte group tree is not visible, use the parent node of the
// contentwrapper
// this is due to not knowing if this was already the case or we
// just changed it
outerWidth = contentWrapper.parentNode.offsetWidth;
}
if (contentWrapper.parentNode.offsetWidth === outerWidth) {
// The group tree is maybe not visible yet. if the width differs, it
// is probably visible
// thus we need to include it
outerWidth -= Number((contentWrapper.style.left || '0px').slice(0, -2));
}
var data = getPageCount.DATA || getPageCount.preliminaryDATA;
var width = data && data.page ? data.page.width : outerWidth;
if (window.getComputedStyle) {
var style = window.getComputedStyle(getId(self.getCurrentPageName(null, '__contentwrapperinner')) || contentWrapper);
outerWidth -= Number(style.paddingLeft.slice(0, -2)) + Number(style.paddingRight.slice(0, -2));
} else {
outerWidth -= 40;
}
return Math.floor(100 / width * outerWidth);
};
/**
* Calculates the optimal height of the current tab so that the page is
* fully visible
*
* @returns {number} height in pixel
*/
this.optimalHeight = function() {
var outerHeight = getId('__contentwrapper').offsetHeight;
var data = getPageCount.DATA || getPageCount.preliminaryDATA;
var height = data && data.page ? data.page.height : outerHeight;
if (window.getComputedStyle) {
var style = window.getComputedStyle(getId(self.getCurrentPageName(null, '__contentwrapperinner')) || getId('__contentwrapper'));
outerHeight -= Number(style.paddingTop.slice(0, -2)) + Number(style.paddingBottom.slice(0, -2));
} else {
outerHeight -= 40;
}
return Math.floor(100 / height * outerHeight);
};
/**
* Set a specific zoom value, make sure the width/height is OK
*
* @param {(string|number)} zoom number in percent, optionally with '%' or a
* specific string value
* @param {Event} event An event that triggered the change. may be null. can
* be used to determine the center point
*/
this.setSafeZoom = function(zoom, event) {
if (zoom == getTranslation("menuBar.zoom.pageFit")) {
var ratioPoint = 5;
var ratio = getPageCount.getPageRatio();
zoom = ratio <= 1 / ratioPoint ? getTranslation("menuBar.zoom.pageWidth") : (ratio > ratioPoint ? getTranslation("menuBar.zoom.pageHeight") : DEFAULTZOOM);
}
this.setZoom(zoom, event);
};
/**
* Before the actual zoom is executed we need to prepare some data
*/
var zoomBeforeAction = function( event ) {
event = event || {};
if ( event.type != "mousewheel" ) {
// We only want scroll events here.
event = {};
}
var zoomEventModel = {};
// now check on the new ScrollPosition
var wrapper = getId('__contentwrapper');
var content = self.getCurrentTab();
zoomEventModel.previousHeight = (content || wrapper).offsetHeight;
zoomEventModel.previousZoom = self.currentZoom;
zoomEventModel.scrollLeft = wrapper.scrollLeft;
zoomEventModel.scrollTop = wrapper.scrollTop;
zoomEventModel.mouseX = event.pageX ? (event.pageX || 0) - wrapper.offsetLeft : wrapper.offsetWidth / 2; // correction for group tree
zoomEventModel.mouseY = (event.pageY || window.innerHeight / 2) - wrapper.offsetTop; // correction for menubar
// debug( 'pos: ' + scrollLeft + ' ' + scrollTop );
// debug( 'move: ' + mouseX + ' ' + mouseY );
return zoomEventModel;
};
/**
* After the actual zoom is executed we need to update some of the view
*/
var zoomAfterAction = function( zoomModel ) {
var wrapper = getId('__contentwrapper');
var content = self.getCurrentTab();
var currentHeight = (content || wrapper).offsetHeight;
// CAlculate the new zoom
var newZoom = zoomModel.previousZoom / zoomModel.previousHeight * currentHeight;
// Move page in relation to zoom
wrapper.scrollLeft = zoomModel.scrollLeft * newZoom / zoomModel.previousZoom;
wrapper.scrollTop = zoomModel.scrollTop * newZoom / zoomModel.previousZoom;
// debug( 'pos 1: ' + wrapper.scrollLeft + ' ' + wrapper.scrollTop );
var moveX = Math.floor( zoomModel.mouseX - zoomModel.mouseX / zoomModel.previousZoom * newZoom );
var moveY = Math.floor( zoomModel.mouseY - zoomModel.mouseY / zoomModel.previousZoom * newZoom );
// debug( 'corr: ' + moveX + ' ' + moveY );
// Correction for the cursor.
wrapper.scrollLeft -= moveX;
wrapper.scrollTop -= moveY;
// debug( 'pos 2: ' + wrapper.scrollLeft + ' ' + wrapper.scrollTop );
};
/**
* Set a specific zoom value
*
* @param {(string|number)} zoom number in percent, optionally with '%' or a
* specific string value
* @param {Event} event An event that triggered the change. may be null. can
* be used to determine the center point
*/
this.setZoom = function(zoom, event) {
if (HASNOZOOM) {
return;
}
switch (zoom) {
case getTranslation("menuBar.zoom.pageWidth"):
zoom = self.optimalWidth() + '%';
break;
case getTranslation("menuBar.zoom.pageHeight"):
zoom = self.optimalHeight() + '%';
break;
case getTranslation("menuBar.zoom.pageFit"):
var width = self.optimalWidth();
var height = self.optimalHeight();
zoom = (width > height ? height : width) + '%';
break;
}
var match = (zoom + '').match(new RegExp("^([0-9]+|([0-9]+)%*)$", "i"));
if (!match || !match[1]) {
self.setZoom(self.currentZoom, event);
return; // No document set?
}
zoom = parseInt(match[2] ? match[2] : match[1]) || 100; // Reset zoom, if not able to parse
var zoomEventModel = zoomBeforeAction( event );
self.currentZoom = zoom;
if (self.currentZoom <= 10) {
self.currentZoom = 10;
}
if (self.currentZoom >= 10000) {
self.currentZoom = 10000;
}
// Disable if too small already
(getId('zoomOut') || document.createElement('div')).setEnabled(self.currentZoom > 10);
// Disable if too large
(getId('zoomIn') || document.createElement('div')).setEnabled(self.currentZoom < 10000);
// Change the content elements for good
var contentWrapper = getId('__contentwrapper');
var contentWrapperInner = (self.getCurrentTab() || contentWrapper).getElementsByClassName('__contentwrapperinner');
for (var i = 0; i < contentWrapperInner.length; i++) {
self.applyZoom(contentWrapperInner[i], getId(self.getCurrentPageName(i + 1)));
}
zoomAfterAction( zoomEventModel );
// Trigger Zoom which should reload a newly visible page
if (getId('endless-mode').isCurrent()) {
contentWrapper.fireEvent('scroll');
}
self.updateZoomStatus(self.currentZoom + '%');
};
/**
* Do a zoom depending on current value and input.
*
* @param {number} change New zoom
* @param {Event} event An event that triggered the change. may be null.
*/
this.zoom = function(change, event) {
change = parseInt(change);
var current = parseInt(self.currentZoom);
/*
* // von 100 als nullpunkt ausgehen change = change/5 + (current -
* 100)^4; change = change^(1/4) + 100;
*/
// Did something change? select the function!
(change != 0 && !isNaN(change) ? self.updateZoom : self.setZoom)(Math.max(10, (change || 0) + current), event);
// Fire Scroll Event for Page Loading in multipage - is done in
// setZoom/applyZoom
/*
* if ( !getId('single-page').isCurrent() ) {
* getId('__contentwrapper').fireEvent('scroll'); }
*/
};
/**
* Save zoom to storage and update the UI
*
* @param {(number|string)} zoom the zoom to set
* @param {Event} event An event that triggered the change. may be null.
*/
this.updateZoom = function(zoom, event) {
// Set the new value for the zoom
$.jStorage.set("menu.zoom", zoom);
self.setZoom(zoom, event);
};
/**
* To be implemented, where needed
*
* @param {object} newPage the page
*/
this.updatePageStatus = function(newPage) {
};
/**
* Make sure that the addressed page is present in the DOM
*
* @param {number} page number of page
* @param {string} fragment URL fragement
* @param {function} finalFunction function to call if page is loaded
*/
this.assurePageIsPresent = function(page, fragment, finalFunction) {
// Multipage Action - The Page HAS TO BE THERE
if (!getId('single-page').isCurrent()) {
// If already loaded, no problem
page = self.getCurrentPageName(page);
var pageElement = page ? getId(page) : null;
if (pageElement && !pageElement.hasBeenLoaded) {
// if not loaded, this has to fire.
pageElement.onLoad = finalFunction;
pageElement.scrollIntoView(true);
} else if (pageElement && typeof finalFunction == 'function') {
// If page is present but already loaded, just call.
finalFunction();
}
return;
}
// Single Page Handling with loadPage Request
self.setPage(page, finalFunction, fragment);
};
this.lastLoadPage = null;
/**
* Set the paging button status. Will disable if needed
*
* @param {number} currentPage the page that is currently set
*/
this.setPagingButtonStatus = function(checkPage) {
var prevEnabled = checkPage > 1 || checkPage == -1;
var nextEnabled = checkPage < parseInt(self.maxPages) || checkPage == -1;
getId('previous').setEnabled(prevEnabled);
getId('first').setEnabled(prevEnabled);
getId('next').setEnabled(nextEnabled || self.maxPages === '');
getId('last').setEnabled(nextEnabled);
if (checkPage >= 0) {
self.updatePageStatus(checkPage + "/" + (self.maxPages || ''));
}
};
/**
* Set the page and update loading status
*
* @param {number} page number of page
* @param {string} fragment URL fragement
* @param {function} finalFunction function to call if page is loaded
*/
this.setPage = function(page, finalFunction, fragment) {
if (!page) {
page = self.currentPageNumber.get() || 1;
} else if (page != self.currentPageNumber.get()) {
page = self.currentPageNumber.set(page);
} else {
// Nothing to do
if (typeof finalFunction == 'function') {
finalFunction(false);
}
// Extra update - may be differently
self.setPagingButtonStatus(page);
return;
}
if (this.lastLoadPage != null) {
// stop? clean old loadPage;
this.lastLoadPage.stopLoading();
}
if (getId('single-page').isCurrent()) {
this.lastLoadPage = new loadpage(page, finalFunction, fragment);
} else {
var element = getId(this.getCurrentPageName(page, '__contentwrapperinner'));
if (element) {
scrollToElement(element);
getId('__contentwrapper').fireEvent('scroll');
}
if (typeof finalFunction == 'function') {
finalFunction(false);
}
}
self.setPagingButtonStatus(page);
};
/**
* Go to first Page
*
* @param {function} finishFunction Function when the page has loaded
* @param {string} fragment (optional) a fragment to jump to after being
* done.
*/
this.firstPage = function(finishFunction, fragment) {
self.setPage(1, finishFunction, fragment);
};
/**
* Go to last Page
*
* @param {function} finishFunction Function when the page has loaded
* @param {string} fragment (optional) a fragment to jump to after being
* done.
*/
this.lastPage = function(finishFunction, fragment) {
self.setPage(self.maxPages, finishFunction, fragment);
};
/**
* Go to next Page
*
* @param {function} finishFunction Function when the page has loaded
* @param {string} fragment (optional) a fragment to jump to after being
* done.
*/
this.nextPage = function(finishFunction, fragment) {
var page = parseInt(self.currentPageNumber.get()) + 1;
if (self.maxPages && page > self.maxPages) {
page = self.maxPages;
}
self.setPage(page, finishFunction, fragment);
};
/**
* Go to previous Page
*
* @param {function} finishFunction Function when the page has loaded
* @param {string} fragment (optional) a fragment to jump to after being
* done.
*/
this.previousPage = function(finishFunction, fragment) {
var page = parseInt(self.currentPageNumber.get()) - 1;
if (page < 1) {
page = 1;
}
self.setPage(page, finishFunction, fragment);
};
/**
* Print the report using our printListener if it is available or use the
* window.print() function otherwise
*/
this.printReport = function() {
if (getPageCount.DATA === null) {
return;
}
( printListener && printListener.showPrint && printListener.showPrint() ) ||
( browserPrint && browserPrint.printInternal && browserPrint.printInternal() );
};
/**
* Reload a report. reset prompts if needed
*/
this.reloadReport = function() {
/* jshint -W020 */
vgen = null;
VARIABLES.cmd = "rfsh";
if (PROMPTONREFRESH) {
VARIABLES.promptonrefresh = true;
}
_pageCache = new pageCache();
/* jshint +W020 */
// Start the loading indicator. Will be removed when the grouptree is
// done.
menubarLoading.start('reload');
self.refreshReport(null);
};
/**
* Should promptonrefresh be displayed or not this is determined by the
* global HASPROMPTS variable
*/
this.activatePromptOnRefresh = function() {
var hasGlobalPrompts = (window.htmlviewer ? window.htmlviewer.HASPROMPTS : HASPROMPTS) || HASPROMPTS; // if the htmlviewer var exists but has not been used
var reloadButtonRef = getId('reload');
if (reloadButtonRef) {
reloadButtonRef.addRemoveClass("hasprompts", hasGlobalPrompts);
}
var promptOnRefreshButtonRef = getId('promptonrefresh');
if (promptOnRefreshButtonRef) {
promptOnRefreshButtonRef.setIsCurrent(PROMPTONREFRESH);
if (hasGlobalPrompts) {
promptOnRefreshButtonRef.addRemoveClass('visible', true);
} else {
promptOnRefreshButtonRef.addRemoveClass('visible', false);
}
}
};
/**
* Is it active? Determined by the global PROMPTONREFRESH variable
*/
this.promptonrefresh = function() {
getId('promptonrefresh').setIsCurrent(!PROMPTONREFRESH);
window.PROMPTONREFRESH = !window.PROMPTONREFRESH;
};
/**
* Resets the grouptree, closes all popups and loads the page again
*
* @param {boolean} resetGroupTree full reset?
* @param {function} finishFunction function when done loading the page
*/
this.refreshReport = function(resetGroupTree, finishFunction) {
(new grouptree(false)).reset(resetGroupTree);
// Close Prompt Dialog
closeAllPopUps && closeAllPopUps();
this.activatePromptOnRefresh();
// Next Action on reload depending on current Mode
if (getId('single-page').isCurrent()) {
self.singlePage(true, finishFunction);
} else {
self.endlessMode(true, finishFunction);
}
keepServerCacheAlive.startPolling(true); // restart the polling
// mechanism
};
this.showKeyBindings = function( event ) {
if ( window.htmlviewer && typeof window.htmlviewer.showShortCuts == 'function' ) {
window.htmlviewer.showShortCuts( event );
return;
}
var popup = new popupHandler().show();
popup.addHeader(getTranslation("keyBinding.header"));
var body = document.createElement('p');
body.appendChild(document.createTextNode(getTranslation("keyBinding.body")));
popup.addBody(body);
KEYBOARD_LISTENER.registeredListener.forEach(function(entry) {
popup.addDetail("[" + entry.keyCode + "]", entry.description, true);
});
};
/**
* Clean the content wrappers, full reset of the report pages
*/
this.resetReportPages = function() {
// Clear the events or they might get triggered instantly resulting in
// massive delays on large reports.
getId('__contentwrapper').clearEvents('scroll');
// Clear current Tab - has to be reloaded anyways
var contentWrapperInner = (self.getCurrentTab() || document).getElementsByClassName('__contentwrapperinner');
var i = contentWrapperInner.length, elem = null;
while (i > 0) {
// Remove Content
elem = getId(self.getCurrentPageName(i, '__contentwrapperinner'));
if (elem) {
elem.hasBeenLoaded = true;
elem.parentElement.removeChild(elem);
} else if (console.error) {
// That should not happen!
console.error("Element not found in reset: " + self.getCurrentPageName(i, '__contentwrapperinner'));
}
// Remove Style-Element if available
var styleElement = getId(self.getCurrentPageName(i, '__pageStyles'));
if (styleElement) {
styleElement.parentNode.removeChild(styleElement);
}
i--;
}
};
/**
* Retursn the currenly active tab
*
* @returns {object} the tab or null
*/
this.getCurrentTab = function() {
var tabPanel = new tabbedPanel();
var tab = tabPanel.managedElements[tabPanel.frontMostTab()];
return tab ? tab.tabWrapper : null;
};
/**
* Enable single page mode
*
* @param {boolean} override true to reset the report though single page
* mode is already the current mode
* @param {function} finishFunction what to do when done loading the first
* page
*/
this.singlePage = function(override, finishFunction) {
if (getId('single-page').isCurrent() && !override) {
return;
}
debug("Going into SINGLE Mode");
self.resetReportPages();
getId('single-page').setIsCurrent(true);
getId('endless-mode').setIsCurrent(false);
// Creat a new first page
self.getCurrentTab().appendChild(self.createInnerContentWrapper());
self.setPage(null, finishFunction);
};
/**
* Enable endless page mode - this could be a huge performance hit for long
* reports
*
* @param {boolean} override true to reset the report though single page
* mode is already the current mode
* @param {function} finishFunction what to do when done loading the first
* page
*/
this.endlessMode = function(override, finishFunction) {
if (getId('endless-mode').isCurrent() && !override) {
return;
}
// Somehow cancel the current loading action
debug("Going into ENDLESS Mode");
self.resetReportPages();
getId('single-page').setIsCurrent(false);
getId('endless-mode').setIsCurrent(true);
self.loadEndlessPage(self.currentPageNumber.get(), finishFunction);
};
/**
* Function to prepare loading all pages and load them when they scroll into
* view
*
* @private
*/
this.buildEndlessPageWithScrollListener = function(finishFunction) {
var internalSelf = self;
var containerElement = getId('__contentwrapper');
var elementInViewport = function(el, mostlyVisible) {
if (!el) {
return false;
}
var elementTop = el.absoluteOffsetTop();
var elementHeight = el.getBoundingClientRect ? el.getBoundingClientRect().height : el.offsetHeight;
if (mostlyVisible) {
// Check of the page is over the half.
var center = windowSize().height / 2;
if ( elementHeight < center ) {
center = elementHeight; // Sync to height of element to prevent jumping of pages
}
// Sync to the middle of the oage or elementHeight if too small
return containerElement.scrollTop + center > elementTop && containerElement.scrollTop + center < elementTop + elementHeight;
}
var containerHeight = containerElement.getBoundingClientRect ? containerElement.getBoundingClientRect().height : containerElement.offsetHeight;
return elementTop < (containerElement.scrollTop + containerHeight) && (elementTop + elementHeight) > containerElement.scrollTop;
};
var addScrollListener = function(pageNumber) {
var timeoutHandler = null;
var handlerFunction = function(me, isTimedCall) {
var pageElement = getId(self.getCurrentPageName(pageNumber));
var pageIsInViewPort = pageElement && elementInViewport(pageElement);
if (pageIsInViewPort) {
var loadFunction = function() {
timeoutHandler = null;
debug("Loading (page is in viewport) for page #" + pageNumber);
containerElement.removeEvent('scroll', handler);
// Should have been canceld a long time ago
if (pageElement.hasBeenLoaded || getId('single-page').isCurrent()) {
return;
}
internalSelf.currentPageNumber.set(pageNumber);
new loadpage(pageNumber, function() {
if (pageElement.onLoad) {
pageElement.onLoad();
}
pageElement.hasBeenLoaded = true;
// Start the cycle again and try to set the correct page number. again.
// containerElement.fireEvent('scroll');
});
};
if (isTimedCall) {
// override !
debug("Timed Call of loadFunction for Page #" + pageNumber);
timeoutHandler = null;
loadFunction();
} else if (timeoutHandler == null) {
// delay
debug("Calling handler again for page #" + pageNumber);
timeoutHandler = window.setTimeout(function(me) {
handlerFunction(me, true);
}, 200);
return;
} else {
debug("this page must have been loaded already #" + pageNumber);
pageElement.hasBeenLoaded = true;
}
} else if (!pageElement) {
debug("canceling scroll event (page not in view and pageElement empty) before loading for page #" + pageNumber);
containerElement.removeEvent('scroll', handler);
} else {
debug("page #" + pageNumber + " not visible.");
}
timeoutHandler = null;
};
// Try if it is in view while construction.
debug("Adding ScrollHandler for page " + pageNumber);
// wait for final event for pages. but set pagenumbers already
var handler = waitForFinalEvent(handlerFunction, 500, "scrollListener#" + pageNumber);
containerElement.addEvent('scroll', handler);
containerElement.addEvent('scroll', function( event ) {
if ( event.noMorePageUpdates ) {
return;
}
var pageElement = getId(self.getCurrentPageName(pageNumber));
if (pageElement && elementInViewport(pageElement, true)) {
event.noMorePageUpdates = true; // we did find a page that is fully displayed. So we do not change the page number any more for this event.
if (pageNumber == self.currentPageNumber.get()) {
return;
}
self.currentPageNumber.set(pageNumber);
self.setPagingButtonStatus(pageNumber);
}
});
handlerFunction();
};
let whenFinishedLoadingPageCount = function(finished) {
// Wait with building the endless-page until the whole document is
// ready.
// This is needed for long running reports.
if (!finished) {
return setTimeout(function() {
getPageCount.supplyWithData(whenFinishedLoadingPageCount);
}, 250);
}
var previousCurrentPage = Math.max(0, self.currentPageNumber.get()); // Needs
// to
// be
// reset
// later
// or
// nothing
// works.
var internalCurrentPage = 0;
// Wait for the correct page count before adding the other pages and
// scroll listener
// Especially when reloading in multipage view we would get the
// wrong page count otherwise
debug("Preparing endless pages. Previous Page was: " + previousCurrentPage);
self.setPagingButtonStatus(-1);
let prepareEndlessPageContent = function() {
if (internalCurrentPage < self.maxPages && getId('endless-mode').isCurrent()) {
internalCurrentPage++;
self.currentPageNumber.set(internalCurrentPage);
self.updatePageStatus("#" + internalCurrentPage);
// What is the current Page???
var nextPage = self.createInnerContentWrapper();
self.getCurrentTab().appendChild(nextPage);
addScrollListener(self.currentPageNumber.get());
// Try to fetch from cache
(new loadpage(internalCurrentPage, function() {
self.currentPageNumber.set(previousCurrentPage);
}, null, true)).startLoading(true);
setTimeout(prepareEndlessPageContent, 1); // Wait
// millisecond
// before
// proceeding.
// Should
// Deblock the
// GUI.
} else {
self.currentPageNumber.set(previousCurrentPage);
self.setPagingButtonStatus(previousCurrentPage);
getPageCount.setSizeOfContent();
menubarLoading.stop('endless-mode');
if (typeof finishFunction == 'function') {
finishFunction();
}
}
};
prepareEndlessPageContent();
};
whenFinishedLoadingPageCount(false);
};
/**
* Endlessly load pages until all are found and in the DOM
*
* @param {function} finishFunction function to execute when all have been
* loaded
*/
this.loadEndlessPage = function(currentPageNumber, finishFunction) {
menubarLoading.start('endless-mode');
var pageNode = self.createInnerContentWrapper(1);
self.getCurrentTab().appendChild(pageNode);
setTimeout(function() {
debug("Starting to load endless page #1");
new loadpage(1, function() {
self.buildEndlessPageWithScrollListener(function() {
self.currentPageNumber.set(1);
self.setPage(currentPageNumber, finishFunction);
});
});
}, 1);
};
/**
* Open a subreport denoted by the link
*
* @param {object} link subreport link
* @param {function} finishFunction Function to call when the subreport is
* open
*/
this.openSubreport = function(link, finishFunction) {
// Get report Variables;
var subreportVariables = {
// subreport_ondemand is encoded here! do not again!
subreport_ondemand : (typeof link.queryKey.subreport_ondemand != 'undefined' ? link.queryKey.subreport_ondemand : null),
subreport : (typeof link.queryKey.subreport != 'undefined' ? link.queryKey.subreport : null),
/*
* 2018-06-21 remove the decode from tabname. It should
* never be encoded but did have problems when the node
* name had percent signs in it.
*/
/*
* 2018-07-17 decode again, it has been encoded in
* grouptree.js
*/
name : (typeof link.queryKey.tabname != 'undefined' ? decodeURIComponent(link.queryKey.tabname) : null)
};
var tabPanel = new tabbedPanel();
tabPanel.createOnlineOnlyTab(subreportVariables, null, finishFunction);
};
/**
* Create an inner Container that may contain the body
*
* @param {number} contentWrapperInnerNumber the number of the inner content
* wrapper to create
* @returns {object} the DOM element
*/
this.createInnerContentWrapper = function(contentWrapperInnerNumber) {
var id = self.getCurrentPageName(contentWrapperInnerNumber, '__contentwrapperinner');
var idElem = getId(id);
if (idElem) {
idElem.parentNode.removeChild(idElem);
return idElem;
}
var contentWrapper = document.createElement('div');
contentWrapper.addClassName('__contentwrapperinner');
contentWrapper.id = id;
var pagewrapper = document.createElement('div');
pagewrapper.addClassName('__pagewrapper');
contentWrapper.appendChild(pagewrapper);
var content = document.createElement('div');
content.addClassName('__content');
content.id = self.getCurrentPageName(contentWrapperInnerNumber);
pagewrapper.appendChild(content);
return contentWrapper;
};
/**
* Update the permissions-based buttons
*
* @param {object} the page permissions
*/
this.updatePermissions = function(permissions) {
var menu = new menubar(), printMenu = (printListener.printMenu || {}).submenu;
if (permissions && typeof documentExport == 'object' && !HASNOEXPORTBUTTON) {
documentExport.enabledFormats = permissions.enabledFormats;
window.CANSHOWPERMALINK = permissions.canShowPermalink != null ? permissions.canShowPermalink : window.CANSHOWPERMALINK;
amIOnline.check(function(isOnline) {
if (isOnline && !documentExport.exportMenu && printMenu && documentExport.exportActions().length > 0) {
documentExport.buildExport(menu.createButton("S", "save", getTranslation("menuBar.save"), documentExport.showExport, printMenu, null, null), printMenu);
}
});
}
// Check permissions.
if ((permissions && !permissions.allowprint) || HASNOPRINTBUTTON) {
// Printing enabled.
if (printMenu && printMenu.id == 'print') {
var nextItem = printMenu.previousSibling;
printMenu.parentNode.removeChild(printMenu);
printMenu = nextItem;
}
}
var numberOfPagesKnown = getPageCount.DATA != null;
// Disable Printing until the report is finished
if (printMenu && printMenu.id == 'print') {
printMenu.setEnabled(numberOfPagesKnown);
}
// Enable the save menu
(getId('save') || document.createElement('div')).setEnabled(numberOfPagesKnown);
// Enable the search menu
(getId('search') || document.createElement('div')).setEnabled(numberOfPagesKnown);
};
};
/*******************************************************************************
* MENU BAR
******************************************************************************/
var _menubar = null;
/**
* The MenuBar - this is a singleton
*
* @class
*/
var menubar = function() {
if (_menubar != null) {
return _menubar;
}
var self = this;
_menubar = this;
this.menubaractions = new MenubarActions();
this.moreTabsSubmenu = null;
this.endlessModeRef = null;
/**
* Create a button for the menubar
*
* @param {(object|string)} accesskey the access key code or object with {
* keycode: {string}, shift: {boolean} }
* @param {string} imageClass CSS class for the image to use and also the ID
* (not twice!)
* @param {string} name Title of the button
* @param {function} action function to execute when pressed or touch ended
* @param {object} insertBefore DOM Object to insert the new button before
* @param {string} hint what the button is about
* @param {object} [insertInto='#__menuBar'] Where to add the button.
* @param {string} [iconClass='__icon'] CSS Class to add for an icon to be
* shown
* @returns {object} the Button
*/
this.createButton = function(accesskey, imageClass, name, action, insertBefore, hint, insertInto, iconClass) {
var button = document.createElement("div");
button.id = imageClass;
button.className = "__menuButton";
if (!insertInto) {
insertInto = getId('__menuBar');
}
if (typeof iconClass == 'undefined') {
iconClass = '__icon';
} else if ( !!name ) {
var nameSpan = document.createElement("span");
nameSpan.appendChild(document.createTextNode(name));
button.appendChild(nameSpan);
}
if (imageClass) {
button.addClassName(iconClass);
button.addClassName(imageClass.split('.').pop());
button.append(document.createElement('i'));
}
if (typeof insertBefore != 'undefined' && insertBefore != null) {
insertInto.insertBefore(button, insertBefore);
} else {
insertInto.appendChild(button);
}
if (hint && hint.length > 0) {
button.setAttribute('data-hint', hint);
} else {
button.title = name;
}
var executeAction = function(e) {
KEYBOARD_LISTENER.stopEvent( e );
button.isEnabled() && action.call( button, e );
};
button.addEvent('click', executeAction);
button.addEvent('touchend', function(e) {
e.preventDefault();
executeAction.call(this, e);
});
if (typeof accesskey == 'string') {
accesskey = {
keycode : accesskey,
shift : false
};
}
if (accesskey && accesskey.keycode.length == 1 && accesskey.keycode >= 'A' && accesskey.keycode <= 'z') {
var keyCode = "CTRL+";
keyCode += accesskey.shift ? "SHIFT+" : '';
keyCode += accesskey.keycode;
if (hint && hint.length > 0) {
button.setAttribute('data-hint', hint + " [" + keyCode + "]");
} else {
button.title += " [" + keyCode + "]";
}
button.setAttribute("tabindex", -1);
var keydownEvent = function(event) {
let keycode;
if (event.which) {
keycode = event.which;
} else {
keycode = event.keyCode;
}
if ((event.metaKey || event.ctrlKey) && ((!event.shiftKey && !accesskey.shift) || (event.shiftKey && accesskey.shift)) && String.fromCharCode(keycode).toLowerCase() == accesskey.keycode.toLowerCase()) {
KEYBOARD_LISTENER.stopEvent( event );
button.focus();
executeAction(event);
}
};
KEYBOARD_LISTENER.addKeyListener(keyCode, name);
window.addEvent('keydown', keydownEvent);
button.clearButton = function() {
this.clearEvents('click');
this.clearEvents('touchend');
window.removeEvent('keydown', keydownEvent);
this.parentNode.removeChild(this);
};
}
return button;
};
/**
* Create a group where, e.g. buttons can be added
*
* @param {object} [insertBefore=null] DOM element to insert the group
* before - at the end if null
* @returns {object} DOM element of the group
*/
this.createGroup = function(insertBefore) {
var group = document.createElement("div");
group.className = '__menuGroup';
if (typeof insertBefore != 'undefined' && insertBefore != null) {
getId('__menuBar').insertBefore(group, insertBefore);
} else {
getId('__menuBar').appendChild(group);
}
return group;
};
/**
* Create a spacer with the size of a button
*
* @param {object} [insertBefore=null] DOM element to insert the group
* before - at the end if null
* @returns {object} DOM element of the group
*/
this.createSpacer = function(insertBefore) {
var spacer = document.createElement("div");
spacer.className = "__menuSpacer";
if (typeof insertBefore != 'undefined') {
getId('__menuBar').insertBefore(spacer, insertBefore);
} else {
getId('__menuBar').appendChild(spacer);
}
return spacer;
};
/**
* Create a dropdown box
*
* @param {string} name Name and ID of the box
* @param {Array} values List of values
* @param {function} action The action to call for a selected value
* @param {string} defaultValue The default value from the list
* @param {object} insertInto Which button to replace with the dropdown
* @param {string} className Extra class names for the dropdown
* @returns {object} The dropdown element
*/
this.createDropDown = function(name, values, action, defaultValue, insertInto, className) {
var dropdown = new editableComboBox(name, className || '');
dropdown.combobox.id = name;
dropdown.combobox.addClassName(className || '');
dropdown.combobox.addClassName("__menuDropDown");
dropdown.updateAction = action;
// Not an object / array? make one!
if (typeof values != 'object') {
values = [ values ];
}
// Add all options
for ( var i = 0; i < values.length; i++) {
dropdown.addOption(values[i], !isNaN(values[i]) ? '%' : '');
}
if (!insertInto) {
insertInto = getId('__menuBar');
}
insertInto.appendChild(dropdown.combobox);
if (typeof dropdown.updateAction == 'function') {
dropdown.updateAction(defaultValue);
}
return dropdown;
};
/**
* Set up the initial state of the menubar and create the buttons
*/
this.init = function() {
// Create Buttonbar
// this.createSpacer().addClassName('hidden-mobile-xs');
var singlePageRef = this.createButton("O", "single-page", getTranslation("menuBar.singlePage"), function() {
self.menubaractions.singlePage();
});
singlePageRef.setIsCurrent(true);
self.endlessModeRef = this.createButton("M", "endless-mode", getTranslation("menuBar.endlessPage"), function() {
self.menubaractions.endlessMode();
});
self.endlessModeRef.setEnabled(false);
// self.endlessModeRef.addClassName('hidden-mobile-xs');
this.createSpacer();
var pageGroup = this.createGroup();
// Page Skip buttons
this.createButton("<<", "first", getTranslation("menuBar.first"), self.menubaractions.firstPage, null, null, pageGroup);
this.createButton("<", "previous", getTranslation("menuBar.previous"), self.menubaractions.previousPage, null, null, pageGroup);
var pageDropper = this.createDropDown("page", [], null, 100, pageGroup);
self.menubaractions.updatePageStatus = pageDropper.updateInputValue;
// Umleiten der Action
// If this is an online Version, print the buttons.
amIOnline.check(function(isOnline) {
// 2021-02-05 - The Edge browser has an issue with printing. That is why we do not register the printing Key "P" anymore
var printKey = (IEVersion().Version.toLowerCase() == 'na' || IEVersion().Version < 20 ) ? "P" : null;
if (!isOnline) {
let printButton = self.createButton(printKey, "print", getTranslation("menuBar.print"), self.menubaractions.printReport, singlePageRef, null, null);
printButton.addClassName('hidden-mobile-xs');
printListener.buildPrint(printButton, singlePageRef, false);
return;
}
//*
self.createButton("R", "reload", getTranslation("menuBar.reload"), self.menubaractions.reloadReport, singlePageRef);
self.createButton({
keycode : 'R',
shift : true
}, "promptonrefresh", getTranslation("menuBar.promptonrefresh"), self.menubaractions.promptonrefresh, singlePageRef, getTranslation("menuBar.promptonrefresh.hint"));
//*/
self.menubaractions.activatePromptOnRefresh();
if (typeof documentSearch == 'object' && !HASNOTEXTSEARCH) {
var searchButton = self.createButton("F", "search", getTranslation("menuBar.search"), documentSearch.showSearch, singlePageRef, null, null);
searchButton.addClassName('hidden-mobile-xs');
documentSearch.buildSearch(searchButton, singlePageRef);
}
self.createSpacer(singlePageRef).addClassName('hidden-mobile-xs');
// self.createSpacer();
// Insert this before the reloadbutton.
let printButton = self.createButton(printKey, "print", getTranslation("menuBar.print"), self.menubaractions.printReport, singlePageRef, null);
printButton.addClassName('hidden-mobile-xs');
printListener.buildPrint(printButton, singlePageRef, true);
self.createSpacer(singlePageRef);
self.menubaractions.updatePermissions();
});
pageDropper.input.parentNode.addClassName('hidden-mobile-xs'); // Page
// selection
// only
// from
// sm
// upwards
pageDropper.input.style.textAlign = 'center';
pageDropper.input.style.width = '65px';
pageDropper.updateAction = function(selectedValue) {
// Match for number or number/number - otherwise nothing is done
var match = selectedValue.match(new RegExp("^([0-9]+|([0-9]+)\/[0-9]+)$", "i"));
var newPage = match ? Math.min(Math.max(1, match[2] ? match[2] : match[1]), self.menubaractions.maxPages) : 1;
if (newPage) {
self.menubaractions.setPage(newPage);
}
};
this.createButton(">", "next", getTranslation("menuBar.next"), self.menubaractions.nextPage, null, null, pageGroup);
this.createButton(">>", "last", getTranslation("menuBar.last"), self.menubaractions.lastPage, null, null, pageGroup);
this.createSpacer();
// Zoom buttons
if (!HASNOZOOM) {
var zoomGroup = this.createGroup();
this.createButton("-", "zoomOut", getTranslation("menuBar.zoomOut"), self.menubaractions.zoomOut, null, null, zoomGroup);
// DEFAULTZOOM because it will be stored again.
var zoomStatus = this.createDropDown("zoom", [ 10, 25, 50, 75, 100, 150, 200, 300, 400, getTranslation("menuBar.zoom.pageWidth"), getTranslation("menuBar.zoom.pageHeight"), getTranslation("menuBar.zoom.pageFit") ], self.menubaractions.updateZoom, DEFAULTZOOM, zoomGroup);
zoomStatus.input.style.width = '114px';
zoomStatus.combobox.addClassName('hidden-mobile');
self.menubaractions.updateZoomStatus = zoomStatus.updateInputValue; // Umleiten der Action
this.createButton("+", "zoomIn", getTranslation("menuBar.zoomIn"), self.menubaractions.zoomIn, null, null, zoomGroup);
}
this.createSpacer();
this.createButton("?", "help", getTranslation("menuBar.help"), self.menubaractions.showKeyBindings, null, null);
};
// Init if there is the menubar wrapper
if (getId('__menuBarWrapper')) {
this.init();
}
};