%PDF- %PDF-
| Direktori : /proc/self/root/data/old/home/stash/atlassian-stash/static/util/ |
| Current File : //proc/self/root/data/old/home/stash/atlassian-stash/static/util/ajax.js |
define('util/ajax', [
'aui',
'jquery',
'underscore',
'util/error',
'util/function',
'util/navbuilder',
'model/page-state',
'widget/error-dialog',
'exports'
], function(
_aui,
$,
_,
errorUtil,
fn,
navbuilder,
pageState,
ErrorDialog,
exports
) {
'use strict';
$.ajaxSetup({
timeout: 60000
});
var errorDialogIsOpen = false;
function afterCountdown($countdownTimeHolder, intervalMs, endDate, afterCountdownFunc) {
var now = new Date();
if (now < endDate) {
var onSecondsChanged = function() {
var secondsLeft = Math.ceil((+endDate - +new Date()) / intervalMs);
if (secondsLeft <= 0) {
clearInterval(intervalId);
afterCountdownFunc();
} else {
$countdownTimeHolder.text(secondsLeft);
}
},
intervalId = setInterval(onSecondsChanged, intervalMs);
onSecondsChanged();
} else {
afterCountdownFunc();
}
}
function hideUntilCountdown($el, $replacementEl, $countdownTimeHolder, intervalMs, endDate) {
var now = new Date();
if (now < endDate) {
$el.addClass('hidden');
$el.before($replacementEl);
afterCountdown($countdownTimeHolder, intervalMs, endDate, function() {
$replacementEl.remove();
$el.removeClass('hidden');
});
}
}
/**
* Adds on global error handling to an ajax request.
*
* If the ajax request returns with global errors, they will be displayed to the user, and the xhr promise will be rejected.
*
* If the error is something that can be fixed with a retry, the error will be displayed, but the xhr promise will NOT be resolved or rejected.
* Instead, progress callbacks will be called with 'stalled' as the argument. If the user attempts a retry, progress
* callbacks will be called with 'unstalled' and the result of a retry request will be used to resolve or reject the original xhr promise.
*
* @param jqXhr the ajax request to handle global errors for
* @param ajaxOptions the options object used in the call to $.ajax. These options are reused if the request needs to be retried.
*/
function ajaxPipe(jqXhr, ajaxOptions, statusHandlers, isRest) {
var pipedXhr,
latestXhr,
latestAbort;
function updateLatest(jqXhr) {
latestXhr = jqXhr;
latestAbort = latestXhr.abort;
latestXhr.abort = abort;
}
function abort() {
latestAbort.apply(latestXhr, arguments);
}
function handleError(error, data, textStatus, jqXhr, errorThrown, ajaxOptions, isRest) {
if (error.shouldLogin) {
// Ideally at this point we want to run as little code as we can to redirect to the log in page ASAP
// with as little interference as possible.
window.onbeforeunload = null;
window.location.href = navbuilder.login().next(window.location.href).build();
return $.Deferred(); // don't resolve|reject
}
if (data) {
delete data.errors;
}
var errorDialog;
if (!errorDialogIsOpen) {
errorDialog = new ErrorDialog();
}
var deferredToReturn = error.shouldRetry && !errorDialogIsOpen ?
$.Deferred() :
$.Deferred().rejectWith(this, [jqXhr, textStatus, errorThrown, data, errorDialog]);
if (!errorDialogIsOpen) {
var extraPanelContent = '',
needsRetryCountdown = false;
var errorHtml = stash.widget.errorContent(error);
errorDialog.addHideListener(function() {
errorDialogIsOpen = false;
});
var dialogOptions = {
id: 'ajax-error',
titleText: error.title,
titleClass: error.titleClass || 'error-header',
showCloseButton : _.isUndefined(error.canClose) ? true : error.canClose,
closeOnOutsideClick : false
};
if (error.fallbackUrl) {
dialogOptions.okButtonText = _aui.escapeHtml(error.fallbackTitle);
errorDialog.addOkListener(function(e) {
window.location.href = error.fallbackUrl;
e.preventDefault();
});
} else if (error.shouldReload) {
dialogOptions.okButtonText = _aui.escapeHtml(AJS.I18n.getText('stash.web.ajax.reload'));
errorDialog.addOkListener(function(e) {
window.location.reload();
e.preventDefault();
});
} else if (error.shouldRetry) {
deferredToReturn.notify('stalled');
if (error.retryAfterDate) {
if (+error.retryAfterDate - +new Date() > 60 * 60 * 1000) {
extraPanelContent = AJS.I18n.getText('stash.web.retry.later');
} else {
needsRetryCountdown = true;
}
}
dialogOptions.okButtonText = _aui.escapeHtml(AJS.I18n.getText('stash.web.ajax.try.again'));
var retryXhr;
errorDialog.addOkListener(function(e) {
deferredToReturn.notify('unstalled');
errorDialog.remove();
retryXhr = ajax(ajaxOptions, isRest);
updateLatest(retryXhr);
// pipe results from the retryXhr straight to the deferredToReturn
retryXhr.done(function() { return deferredToReturn.resolveWith(this, arguments); });
retryXhr.fail(function() { return deferredToReturn.rejectWith(this, arguments); });
e.preventDefault();
});
errorDialog.addHideListener(function() {
if (deferredToReturn.state() === 'pending' && !retryXhr) {
deferredToReturn.rejectWith(this, [jqXhr, textStatus, errorThrown, data]);
}
});
} else {
// if the Ok button doesn't do anything but close the dialog, hide the second Close button.
dialogOptions.showCloseButton = false;
}
dialogOptions.panelContent = '<p>' + errorHtml + extraPanelContent + '</p>';
errorDialog.reinit(dialogOptions).show();
errorDialogIsOpen = true;
if (needsRetryCountdown) {
var intervalMs, retryInHtml;
if (+error.retryAfterDate - +new Date() > 60 * 1000) {
retryInHtml = AJS.I18n.getText('stash.web.retry.in.x.minutes', '<time><span></span>', '</time>');
intervalMs = 60 * 1000;
} else {
retryInHtml = AJS.I18n.getText('stash.web.retry.in.x.seconds', '<time><span></span>', '</time>');
intervalMs = 1000;
}
var $retryMessage = $('<span>' + retryInHtml + '</span>'),
$intervalHolder = $retryMessage.children('time').children();
hideUntilCountdown(errorDialog.getOkButton(), $retryMessage, $intervalHolder, intervalMs, error.retryAfterDate);
}
}
return deferredToReturn;
}
function xhrPipe(data, textStatus, jqXhr, errorThrown, customHandler, fallbackFunc) {
var error = isRest ?
errorUtil.getDominantRESTError(data, jqXhr) :
errorUtil.getDominantAJAXError(jqXhr),
handleErrors = true;
if (customHandler) {
var ret = customHandler(error);
// custom handler can return a deferred which will be piped through. We won't handle errors
if (ret && typeof ret.promise === 'function') {
return ret.promise(jqXhr);
}
// custom handler can return a replacement error object which will replace the one we generate
if (ret && _.isObject(ret)) {
error = ret;
}
// if the custom handler returns false, we won't handle errors,
// and will simply fallback to normal behavior
handleErrors = ret !== false;
}
if (handleErrors && error) {
return handleError(error, data, textStatus, jqXhr, errorThrown, ajaxOptions, isRest);
} else {
return fallbackFunc();
}
}
function getStatusHandler(status) {
var customHandler = statusHandlers[status];
if (customHandler === undefined || customHandler === null) {
customHandler = statusHandlers['*'];
}
if (typeof customHandler === 'function') {
return customHandler;
} else {
// Allow status handlers to be non-functions (ie false), which should always be returned
return fn.constant(customHandler);
}
}
function done(data, textStatus, jqXhr) {
var self = this;
var customHandler = getStatusHandler(jqXhr.status),
callCustomHandler = customHandler ? _.bind(customHandler, self, data, textStatus, jqXhr) : null;
return xhrPipe(data, textStatus, jqXhr, null, callCustomHandler, function() {
return $.Deferred().resolveWith(self, [ data, textStatus, jqXhr ]);
});
}
function fail(jqXhr, textStatus, errorThrown) {
var self = this;
var data = jqXhr.responseText;
try {
data = JSON.parse(data);
} catch(e) {}
var customHandler = getStatusHandler(jqXhr.status),
callCustomHandler = customHandler ? _.bind(customHandler, self, jqXhr, textStatus, errorThrown, data) : null;
return xhrPipe(data, textStatus, jqXhr, errorThrown, callCustomHandler, function() {
return $.Deferred().rejectWith(self, [ jqXhr, textStatus, errorThrown, data ]);
});
}
updateLatest(jqXhr);
pipedXhr = jqXhr.then(done, fail);
// return the original xhr, but with the piped done|fail|notify methods.
return pipedXhr.promise(jqXhr);
}
function ajax(options, internalIsRest) {
var statusHandlers;
if (options.statusCode) {
statusHandlers = options.statusCode;
delete options.statusCode;
}
statusHandlers = statusHandlers || {};
var xhr = ajaxPipe($.ajax(options), options, statusHandlers, internalIsRest);
xhr.statusCode = function(map) {
if (map) {
if (xhr.state() === 'pending') {
$.extend(statusHandlers, map);
} else {
for(var prop in map) {
if (map.hasOwnProperty(prop)) {
_aui.log('xhr.statusCode() should not be called after the request has completed. ' +
'Your handler will have no affect on the resolution of the request.');
break;
}
}
var tmp = map[ xhr.status ];
xhr.then( tmp, tmp );
}
}
};
return xhr;
}
function rest(options) {
var headers = {};
if (pageState.getCurrentUser()) {
headers['X-AUSERNAME'] = pageState.getCurrentUser().getName();
headers['X-AUSERID'] = pageState.getCurrentUser().getId();
}
options = $.extend(true, {
dataType: 'json',
contentType: 'application/json',
headers: headers,
jsonp: false,
type : "GET"
}, options);
if (options.type.toUpperCase() !== 'GET' && ($.isPlainObject(options.data) || $.isArray(options.data))) {
options.data = JSON.stringify(options.data);
}
return ajax(options, true);
}
// turn form inputs into [{name:'blah', value:'blah'}, ...] with serializeArray,
// then into { blah: 'blah', ...} via reduce
function formToJSON($form) {
// Find all the checked checkboxes with the value 'on' and store them in an object
var checkboxes = _.reduce($form.find('input[type=checkbox]:checked'), function(obj, entry) {
var $entry = $(entry);
// Only process checkboxes with 'on' which is the default for Chrome/Firefox/IE9
if ($entry.attr('value') === 'on') {
obj[$entry.attr('name')] = true;
}
return obj;
}, {});
return _.reduce($form.serializeArray(), function(obj, entry) {
//paraphrased from http://stackoverflow.com/a/1186309/37685
var existingVal = obj[entry.name],
newVal = entry.value === undefined ? '' : entry.value;
// Override the checkbox value (most likely 'on') with true
if (checkboxes[entry.name]) {
newVal = true;
}
if (existingVal !== undefined) {
// make it an array if it's not, since we have multiple values.
if (!$.isArray(existingVal)) {
obj[entry.name] = [ existingVal ];
}
// add the new value to the array
obj[entry.name].push(newVal);
} else {
obj[entry.name] = newVal;
}
return obj;
}, {
//seed with new object
});
}
function poll(options) {
options = $.extend({
pollTimeout: 60000,
interval: 500,
delay : 0,
tick: $.noop
}, options);
var paused = false;
var polling = false;
var defer = $.Deferred(),
startTime = new Date().getTime(),
doPoll = function() {
// Short circuit if the poller is paused or if it is already polling
if (paused || polling) {
return;
}
polling = true;
rest(options).done(function(data, textStatus, xhr) {
var isDone = options.tick(data, textStatus, xhr);
if (isDone) {
defer.resolveWith(this, [data, textStatus, xhr]);
} else if ((new Date().getTime() - startTime) > options.pollTimeout || typeof isDone !== 'undefined') {
defer.rejectWith(this, [xhr, textStatus, null, data]);
} else {
setTimeout(doPoll, options.interval);
}
}).fail(function(xhr, textStatus, errorThrown, data) {
defer.rejectWith(this, [xhr, textStatus, errorThrown, data]);
}).always(function() {
polling = false;
});
};
setTimeout(doPoll, options.delay);
var promise = defer.promise();
promise.resume = function() {
if (paused) {
paused = false;
doPoll();
}
};
promise.pause = function() {
paused = true;
};
return promise;
}
exports.ignore404WithinRepository = function (callback) {
return {
'404': function (xhr, testStatus, errorThrown, data, fallbackError) {
var error = data && data.errors && data.errors.length && data.errors[0];
if (errorUtil.isErrorEntityWithinRepository(error)) {
return callback && callback(data) || false; // don't handle this globally.
}
}
};
};
exports.ajax = ajax;
exports.rest = rest;
exports.poll = poll;
exports.formToJSON = formToJSON;
});