/*
* Copyright (C) 2005 - 2011 Jaspersoft Corporation. All rights reserved.
* http://www.jaspersoft.com.
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
/**
* @author Angus Croll
* Generic Ajax Utils
*/
/**
* Global Ajax Object
*/
var ajax = {};
//cancel all requests sent before this date (in ms)
ajax.cancelRequestsBefore;
ajax.LOADING_ID = "loading";
/* Headers needed by CSRF Guard */
/* {"":"", "":"", ...} */
ajax.csrfRequestHeaders = new Hash();
/* Set CSRF headers to XmlHttpRequest object */
ajax.setCsrfHeaders = function(xmlhttp) {
ajax.csrfRequestHeaders.each(function(pair) {
xmlhttp.setRequestHeader(pair.key, pair.value);
});
}
/**
* @class Manages incoming Ajax requests and processes corresponding Ajax responses
* based on specified attributes.
* Responses are bound to to the instance of AjaxRequester created by the corresponing request
*
* @constructor
* @param {String} url address for server request
* @param {Array} params [1]fillLocation [2]fromLocation [3]Array of callbacks
* @param {String} postData user data for posting - where applicable
* @return a new AjaxRequester instance
* @type AjaxRequester
*/
function AjaxRequester(url, params, postData, synchronous) {
this.url = url;
this.params = params;
this.xmlhttp = getXMLHTTP();
var rsChangeFunction = this.processResponse(this);
this.xmlhttp.onreadystatechange = rsChangeFunction;
this.postData = postData;
this.async = !synchronous;
this.requestTime = +new Date; //(new Date).getTime()
}
/////////////////////////////////////////////////////////////////////////
// Prototype Augmentation
/////////////////////////////////////////////////////////////////////////
AjaxRequester
//constants for targetted update modes
.addVar('CUMULATIVE_UPDATE','c')
.addVar('ROW_COPY_UPDATE','r')
.addVar('TARGETTED_REPLACE_UPDATE','t')
.addVar('EVAL_JSON','j')
.addVar('DUMMY_POST_PARAM','dummyPostData')
.addVar('MAX_WAIT_TIME',2000)
//default function assignment
.addVar('errorHandler',function() {return false}) //the function to validate and report errors
/**
* Submit an ajax get request
* @private
* @return true if successful
* @type boolean
*/
.addMethod('doGet', function() {
if (this.xmlhttp) {
this.xmlhttp.open("GET",this.url ,this.async);
this.xmlhttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
this.xmlhttp.setRequestHeader( "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT" );
this.xmlhttp.setRequestHeader("x-requested-with","AJAXRequest");
ajax.setCsrfHeaders(this.xmlhttp);
this.xmlhttp.send(null);
return true;
}
return false;
})
/**
* Submit an ajax post request
* @private
* @return true if successful
* @type boolean
*/
.addMethod('doPost', function() {
if (this.xmlhttp) {
if (this.postData===AjaxRequester.prototype.DUMMY_POST_PARAM) {
this.postData=null;
}
this.xmlhttp.open("POST",this.url,this.async);
this.xmlhttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
this.xmlhttp.setRequestHeader("x-requested-with","AJAXRequest");
ajax.setCsrfHeaders(this.xmlhttp);
this.xmlhttp.send(this.postData);
return true;
}
return false;
})
/**
* Function to process the detected Ajax response
* @param {AjaxRequester} the instance who's request we are processing
* @private
* @return the handler function
* @type function
*/
.addMethod('processResponse', function(requester) {
return function(){
if (requester.xmlhttp.readyState == 4) {
if(ajax.cancelRequestsBefore && (ajax.cancelRequestsBefore > requester.requestTime)) {
//ignore this request
ajaxRequestEnded(requester);
return;
}
//if (requester.verifyAjaxResponse()) {
handleResponse(requester);
//}
}
};
})
/**
* Set error handler for this requester
* @param {function} the error handler function
* @private
*/
.addMethod('setErrorHandler', function(setErrorHandler) {
this.errorHandler = errorHandler;
})
.addMethod('verifyAjaxResponse', function() {
//prompt if no server
return this.xmlhttp.getResponseHeader('Server') || this.confirmContinue();
})
/**
* Start countdown to "no response" message
*/
.addMethod('startResponseTimer', function() {
this.responseTimer = window.setTimeout(function(){dialogs.popup.show($(ajax.LOADING_ID),true)},this.MAX_WAIT_TIME);
})
/**
* Cancel countdown to "no response" message
*/
.addMethod('cancelResponseTimer', function() {
window.clearTimeout(this.responseTimer);
})
/**
* Cancel countdown to "no response" message
*/
.addMethod('confirmContinue', function() {
return confirm(serverIsNotResponding);
});
/////////////////////////////////////////////////////////////////////////
// Prototype Augmentation; End
/////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// AjaxRequester: End
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// Global Space: Start
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// Response Handling
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Delegate to handler for ajax response
* @param {AjaxRequester} the active requester instance
* @param {Array} parameters bundled with the request
* @private
*/
function handleResponse(requester) {
checkForErrors(requester) || requester.responseHandler(requester);
ajaxRequestEnded(requester);
if (JRS && JRS.vars){
JRS.vars.ajax_in_progress = false;
}
if(document.getElementById('mainTableContainerOverlay')) document.getElementById('mainTableContainerOverlay').className = 'hidden';
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Response Handlers
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Default handler for responses triggered by targeted ajax requests
* @private
* @param {AjaxRequester} requester the active requester instance
* @param {Array} params parameters bundled with the request
*/
function targettedResponseHandler(requester) {
var xmlhttp = requester.xmlhttp;
var fillId = requester.params[0];
var fromId = requester.params[1];
var callback = requester.params[2];
var isAutomaticRefresh = requester.params[3];
var toLocation = $(fillId);
if(typeof Report !== 'undefined') Report.delayedRender = null;
if (fromId) {
/*
* Check if the user is interacting with the report. If yes postpone updating #reportContainer until jive
* triggers "jive_inactive" event.
*/
if(isAutomaticRefresh && jive && jive.active) {
Report.delayedRender = (function(x,f,t){
return function(){
toLocation.innerHTML="";
updateUsingResponseSubset(x,f,t);
}
})(xmlhttp, fromId, toLocation);
} else {
if (jive && jive.started) {
jive.hide();
}
toLocation.innerHTML="";
updateUsingResponseSubset(xmlhttp, fromId, toLocation);
}
} else {
//we want all the retrieved content - throw it all in
toLocation.innerHTML = xmlhttp.responseText;
}
invokeCallbacks(callback, toLocation);
}
/**
* Handler adds new content to existing content in specified container
* @private
* @param {AjaxRequester} requester the active requester instance
* @param {Array} params parameters bundled with the request
*/
function cumulativeResponseHandler(requester) {
var xmlhttp = requester.xmlhttp;
var fillId = requester.params[0];
var fromId = requester.params[1];
var callback = requester.params[2];
var toLocation = $(fillId);
if (fromId) {
updateUsingResponseSubset(xmlhttp, fromId, toLocation);
} else {
//we want all the retrieved content - throw it all in
toLocation.insert(xmlhttp.responseText, {position: 'after'}); //note this will eval scripts too
}
invokeCallbacks(callback);
}
/**
* Use this handler if the response text represents table rows to add to an existing rows in the specified container
* (more efficient than appending innerHtml)
* @private
* @param {AjaxRequester} requester the active requester instance
* @param {Array} params parameters bundled with the request
*/
function rowCopyResponseHandler(requester) {
var xmlhttp = requester.xmlhttp;
var tableId = requester.params[0];
//var newRowId = params[1]; //not used yet
var callback = requester.params[2];
var theTable = $(tableId);
if (theTable.tagName !== "TABLE") {
alert("Ajax Exception: rowCopyResponseHandler will not work for container " + theTable.tagName);
return;
}
//put response html into a temp div to form the table
//var tempDiv = document.createElement('DIV');
var tempDiv = Builder.node('DIV');
tempDiv.innerHTML = xmlhttp.responseText;
//now copy to existing table body
copyTable(tempDiv.getElementsByTagName('table')[0], theTable, false, false);
invokeCallbacks(callback);
}
/**
* Special handler for when we want to overwrite entire page with response
* @param {AjaxRequester} the active requester instance
* @param {Array} parameters bundled with the request
* @private
*/
// TODO Consider for removal
var clobberingResponseHandler = function(requester) {
var callback = requester.params[2];
document.body.innerHTML = requester.xmlhttp.responseText;
document.fire("dom:loaded");
invokeCallbacks(callback);
}
/**
* Handler evals JSOn expression. Does not update markup in anyway
* For any updates to occur, responseText expression must include an assignment
* e.g. "var myProfile = {city: London, age: 39, hobbies: ['waterskiing','chess']}"
* @param {AjaxRequester} the active requester instance
* @param {Array} parameters bundled with the request
* @private
*/
var evalJSONResponseHandler = function(requester) {
var jSONResponse = null;
try {
jSONResponse = requester.xmlhttp.responseText.evalJSON()
} catch (e) {
window.console && console.log(e);
}
var callback = requester.params[2];
invokeCallbacks(callback, jSONResponse);
}
function updateUsingResponseSubset(xmlhttp, fromLocation, toLoc) {
/**
* This prevents the script tags inside responseText to be executed;
* If we wanted them to be executed, we would have wrapped the responseText with a div like this:
* var response = jQuery('').html(xmlhttp.responseText);
*/
var response = jQuery(xmlhttp.responseText);
var whatWeWant = response.filter('#' + fromLocation);
if ((toLoc.tagName=="TABLE") && ($(fromLocation).tagName=="TABLE")) {
copyTableJquery(whatWeWant, toLoc, true);
} else {
jQuery(toLoc).append(whatWeWant.html());
}
/*
* Check for the existence of Fusioncharts rendering code and execute it.
*/
if(typeof(jQuery) !== 'undefined'){
response.filter('script.fusioncharts').each(function() {
eval(jQuery(this).html());
});
}
/*
* jasperreports interactive: load JavaScript scripts synchronously
*/
if(typeof(jQuery) !== 'undefined' && true){
var scriptTags = response.filter('script.jasperreports'),
sz = scriptTags.size(),
idx = 0;
function iterate() {
if (idx >= sz) {
return;
}
var scriptObj = jQuery(scriptTags.get(idx));
if (scriptObj.attr('src')) {
idx++;
loadScript(scriptObj.attr('data-custname'), scriptObj.attr('src'), iterate);
} else {
idx++;
executeScript(scriptObj.html(), iterate);
}
}
function loadScript(scriptName, scriptUrl, callbackFn) {
var gotCallback = callbackFn || false,
scriptElement = document.createElement('script');
// prevent the script tag from being created more than once
if (!window.jr_scripts) {
window.jr_scripts = {};
}
if (!window.jr_scripts[scriptName] && scriptName !== 'jr_jq_min') { // skips jQuery core script
scriptElement.setAttribute('type', 'text/javascript');
if (scriptElement.readyState){ // for IE
scriptElement.onreadystatechange = function(){
if (scriptElement.readyState === 'loaded' || scriptElement.readyState === 'complete'){
scriptElement.onreadystatechange = null;
if (gotCallback) {
callbackFn();
}
}
};
} else { // for Others - this is not supposed to work on Safari 2
scriptElement.onload = function(){
if (gotCallback) {
callbackFn();
}
};
}
scriptElement.src = scriptUrl;
document.getElementsByTagName('head')[0].appendChild(scriptElement);
window.jr_scripts[scriptName] = scriptUrl;
} else if (gotCallback) {
callbackFn();
}
}
function executeScript(scriptString, callbackFn) {
var gotCallback = callbackFn || false;
if (scriptString) {
var chartId;
var pageChildren;
var lines = scriptString.match(/^.*((\r\n|\n|\r)|$)/gm);
for(var i=0;i= 0) {
jQuery(window).on('resizeEnd',function(){new Highcharts.Chart(window.charts[chartId]);console.info(window.charts[chartId].chart.height);});
jQuery('#reportContainer').css({position:'absolute',top:0,right:0,bottom:0,left:0});
jQuery('div.jrPage').css({width:'auto',height:'auto',position:'absolute',top:'32px',right:'4px',bottom:0,left:'4px'});
pageChildren = jQuery('div.jrPage').children();
if(pageChildren.length == 2) {
pageChildren.eq(0).css({width:'100%',height:'auto',position:'absolute',top:'-22px',height:'24px'});
pageChildren.children().eq(0).css({position:'relative',width:'100%'});
pageChildren.eq(1).css({width:'auto',height:'auto',position:'absolute',top:0,right:0,bottom:0,left:0});
} else {
pageChildren.eq(0).css({width:'auto',height:'auto',position:'absolute',top:0,right:0,bottom:0,left:0});
}
jQuery('div.jrPage').find('div.highcharts_parent_container').eq(0).css({position:'absolute',top:0,right:0,bottom:0,left:0});
chartId = lines[i].substring(3,lines[i].indexOf('='));
lines.splice(i+1,0,'window.charts["'+chartId+'"] = '+chartId+';');
}
if(chartId && (lines[i].indexOf('chart.width') >= 0 || lines[i].indexOf('chart.height') >= 0)) lines[i] = '//' + lines[i];
}
window.eval(lines.join('\n'));
if (gotCallback) {
callbackFn();
}
}
}
iterate();
}
}
function invokeCallbacks(callback, customArg) {
if (callback) {
typeof(callback) === 'string' ? eval(callback) : callback(customArg);
}
}
/////////////////////////////////////////////////////////////////////////////////////////
// Public API starts Here....
// Please do not amend exisiting signatures but feel free to extend the API as required
/////////////////////////////////////////////////////////////////////////////////////////
/**
* Send an ajax request with this URL and update the entire page with the response
* @param {String} url of the request
*/
// TODO Consider for removal
function ajaxClobberredUpdate(url,options) {
options.responseHandler = clobberingResponseHandler;
ajaxUpdate(url, options);
}
/**
* Send an ajax request with this URL and update the targetContainer with the sourceContainer container of the response DOM
* Optionally execute the post fill action as a callback following response processing
* @param {String} url the url of the request
* @param {String} targetContainer id indicating where to dump html response
* @param {String} sourceContainer id indicating which part of the html response to use
* @param {Array} callback JS functions to evaluate after ajax update
* @param {function} errorHandler a function to evaluate to trap errors
* @param {String} postData user data for posting - where applicable
* @param {String} update mode (default is targetted replace)
*/
function ajaxTargettedUpdate(url,options) {
var responseHandler;
if (options.mode == AjaxRequester.prototype.CUMULATIVE_UPDATE) {
responseHandler = cumulativeResponseHandler;
} else if (options.mode == AjaxRequester.prototype.ROW_COPY_UPDATE) {
responseHandler = rowCopyResponseHandler;
} else if (options.mode == AjaxRequester.prototype.EVAL_JSON) {
responseHandler = evalJSONResponseHandler;
} else {
responseHandler = targettedResponseHandler;
}
options.responseHandler = function (requester, params) {
if (options.preFillAction) {
if (typeof(options.preFillAction) == 'string') {
eval(options.preFillAction);
} else {
options.preFillAction(responseHandler(requester, params));
}
} else {
responseHandler(requester, options.params);
}
}
ajaxUpdate(url, options);
}
/**
* Send an ajax request with this URL but don't return any content to the sender
* Optionally execute the post fill action as a callback following response processing
* @param {String} url the url of the request
* @param {String} targetContainer id indicating where to dump html response
* @param {String} sourceContainer id indicating which part of the html response to use
* @param {Array} callback JS functions to evaluate after ajax update
* @param {function} errorHandler a function to evaluate to trap errors
* @param {String} postData user data for posting - where applicable
*/
function ajaxNonReturningUpdate(url,options) {
options.responseHandler = null;
ajaxUpdate(url,options);
}
/**
* Submit the form and replace entire page
* @param {String} form name of the form
* @param {String} url the url of the request
* @param {String} extraPostData form data for posting
* @param {String} targetContainer id indicating where to dump html response
* @param {String} sourceContainer id indicating which part of the html response to use
* @param {Array} callback JS functions to evaluate after ajax update
* @param {function} errorHandler a function to evaluate to trap errors
*/
function ajaxClobberedFormSubmit(form, url, options)
{
var postData = getPostData(form, extraPostData);
options.postData = appendPostData(postData, extraPostData);
options.responseHandler = clobberingResponseHandler;
ajaxUpdate(url, options);
}
/**
* Submit the form and update the targetContainer with the sourceContainer container of the response DOM
* @param {String} form name of the form
* @param {String} url the url of the request
* @param {String} extraPostData form data for posting
* @param {String} targetContainer id indicating where to dump html response
* @param {String} sourceContainer id indicating which part of the html response to use
* @param {Array} callback JS functions to evaluate after ajax update
* @param {function} errorHandler a function to evaluate to trap errors
*/
function ajaxTargettedFormSubmit(form, url, options)
{
var postData = getPostData(form, options.extraPostData);
options.postData = appendPostData(postData,options.extraPostData);
options.responseHandler = targettedResponseHandler;
ajaxUpdate(url, options);
}
function ajaxErrorHandler() {
showMessageDialog(ajaxError, ajaxErrorHeader);
}
////////////////////////////////////////////////////////////////////////////////
// ...Public API ends Here....
////////////////////////////////////////////////////////////////////////////////
/**
* @private
*/
//dummy response handler for non returning case
function doNothing() {
}
/**
* Wrapper for AjaxRequester
* @private
* @param {String} url - the url of the request
* @param {Object} options - an object literal optioanlly defining:
* @option {function} responseHandler - the designated response handler
* @option {String} fillLocation - id indicating where in the DOM to dump ajax response
* @option {String} fromLocation - id indicating which part of the ajax response to use
* @option {Array} callback - JS functions to evaluate after ajax update
* @option {function} errorHandler - the designated error handler
* @option {String} postData - user data for posting - where applicable
*/
function ajaxUpdate(url, options){
var ok = true;
var requester = new AjaxRequester(url, [options.fillLocation, options.fromLocation, options.callback, options.isAutomaticRefresh], options.postData,
options.synchronous);
if(isIPad() && JRS.vars.current_flow == 'adhoc') {
if(!JRS.vars.ajax_in_progress) {
JRS.vars.ajax_in_progress = true;
}
}
if(ok){
requester.busyCursor = !options.silent;
requester.showLoading = !options.silent && !options.hideLoader;
requester.responseHandler = options.responseHandler || doNothing;
if (requester.responseHandler != doNothing) {
ajaxRequestStarted(requester);
}
if (options.errorHandler) {
requester.errorHandler = options.errorHandler;
}
if (requester.xmlhttp) {
if (options.postData) {
requester.doPost();
}
else {
requester.doGet();
}
}
}
}
/**
* @private
*/
function checkForErrors(requester) {
var errorHandler = requester.errorHandler;
return errorHandler(requester.xmlhttp);
}
/**
* @private
*/
function getPostData(form, extraPostData)
{
if (typeof form == 'string')
{
form = document.forms[form];
}
var data = "";
for (var i = 0; i < form.elements.length; ++i)
{
var element = form.elements[i];
if (element.name && !(extraPostData && extraPostData[element.name]))
{
data = appendFormInput(data, element);
}
}
return data;
}
/**
* @private
*/
function appendPostData(postData, extraPostData){
for (name in extraPostData) {
postData = appendFormValue(postData, name, extraPostData[name]);
}
return postData;
}
/**
* @private
*/
function appendFormInput(data, element){
if (element.name) {
var value;
var append = false;
switch (element.type) {
case "checkbox":
case "radio":
append = element.checked;
value = element.value;
break;
case "hidden":
case "password":
case "text":
case "Textarea":
append = true;
value = element.value;
break;
case "select-one":
case "select-multiple":
value = new Array();
for (var i = 0; i < element.options.length; ++i) {
var option = element.options[i];
if (option.selected) {
append = true;
value.push(option.value);
}
}
break;
}
if (append) {
if (value.shift) {
while (value.length > 0) {
data = appendFormValue(data, element.name, value.shift());
}
}
else {
data = appendFormValue(data, element.name, value);
}
}
}
return data;
}
/**
* @private
*/
function appendFormValue(data, name, value){
if (data.length > 0) {
data += "&";
}
data += name + "=" + encodeURIComponent(value);
return data;
}
/**
* @private
*/
function baseErrorHandler(ajaxAgent){
//Handling HTTP 500 - Internal server error
if (ajaxAgent.status == 500) {
showErrorPopup(ajaxAgent);
return true;
}
var sessionTimeout = ajaxAgent.getResponseHeader("LoginRequested");
if (sessionTimeout) {
var newloc = '.';
document.location = newloc;
return true;
}
var isErrorPage = ajaxAgent.getResponseHeader("JasperServerError");
if (isErrorPage) {
var suppressError = ajaxAgent.getResponseHeader("SuppressError");
if (!suppressError) {
showErrorPopup(ajaxAgent);
}
return true;
}
return false;
}
var ERROR_POPUP_DIV = "jsErrorPopup";
var ERROR_POPUP_CONTENTS = "errorPopupContents";
var ERROR_POPUP_BACK_BUTTON = "errorBack";
var ERROR_POPUP_CLOSE_BUTTON = "errorPopupCloseButton";
/**
* @private
*/
function showErrorPopup(ajaxAgent)
{
dialogs.errorPopup.show(ajaxAgent.responseText);
}
/**
* @private
*/
function hideErrorPopup()
{
popOverlayObject();
var errorPopup = document.getElementById(ERROR_POPUP_DIV);
errorPopup.style.display = "none";
}
////////////////////////////////////////////////////////////////////////////////
// Request counter
////////////////////////////////////////////////////////////////////////////////
ajax.ajaxRequestCount = 0;
/**
* @private
*/
function ajaxRequestStarted(requester) {
++ajax.ajaxRequestCount;
requester.busyCursor && (document.body.style.cursor = "wait");
!isIPad() && requester.showLoading && requester.startResponseTimer();
}
/**
* @private
*/
function ajaxRequestEnded(requester) {
requester.cancelResponseTimer();
if (ajax.ajaxRequestCount <= 1) {
document.body.style.cursor = "auto";
ajax.ajaxRequestCount = 0;
dialogs.popup.hide($(ajax.LOADING_ID));
} else {
ajax.ajaxRequestCount--;
}
}
////////////////////////////////////////////////////////////////////////////////
// XMLHTTP
////////////////////////////////////////////////////////////////////////////////
//
// standard function to obtain an xmlhttp instance regardless of platform
//
function getXMLHTTP() {
var alerted;
var xmlhttp;
/*@cc_on @*/
/*@if (@_jscript_version >= 5)
// JScript gives us Conditional compilation, we can cope with old IE versions.
try {
xmlhttp=new ActiveXObject("Msxml2.XMLHTTP")
} catch (e) {
try {
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP")
} catch (E) {
alert("You must have Microsofts XML parsers available")
}
}
@else
alert("You must have JScript version 5 or above.")
xmlhttp=false
alerted=true
@end @*/
if (!xmlhttp && !alerted) {
// Non ECMAScript Ed. 3 will error here (IE<5 ok), nothing I can
// realistically do about it, blame the w3c or ECMA for not
// having a working versioning capability in