Avoid concurrent AJAX requests and show loading indication

This commit is contained in:
Martchus 2022-03-12 18:53:46 +01:00
parent ac0d64fe3e
commit 6437c7eb42
5 changed files with 68 additions and 8 deletions

View File

@ -48,6 +48,7 @@ header nav li {
} }
header nav ul li a, header nav ul li a:link, header nav ul li a:visited { header nav ul li a, header nav ul li a:link, header nav ul li a:visited {
display: inline-block; display: inline-block;
position: relative;
box-sizing: border-box; box-sizing: border-box;
text-decoration: none; text-decoration: none;
padding-left: 20px; padding-left: 20px;
@ -62,6 +63,39 @@ header nav ul li a:hover, header nav ul li.active a {
color: #fff; color: #fff;
text-decoration: none; text-decoration: none;
} }
header nav ul li.progress a:after {
content: '';
box-sizing: border-box;
position: absolute;
left: 0px;
bottom: 0px;
height: 5px;
width: 100%;
background: #08c;
animation: loading 2s infinite;
}
@keyframes loading {
0% {
margin-left: 0%;
width: 30%;
}
15% {
margin-left: 35%;
width: 50%;
}
50% {
margin-left: 70%;
width: 30%;
}
65% {
margin-left: 15%;
width: 50%;
}
100% {
margin-left: 0%;
width: 30%;
}
}
header nav li.active { header nav li.active {
background-color: #000; background-color: #000;
} }
@ -99,4 +133,4 @@ main {
section { section {
background-color: white; background-color: white;
padding: 10px; padding: 10px;
} }

View File

@ -1,12 +1,34 @@
export const apiPrefix = 'api/v0'; export const apiPrefix = 'api/v0';
let authError = false; let authError = false;
let ongoingRequests = {};
/// \brief Makes an AJAX query with basic error handling. /// \brief Makes an AJAX query with basic error handling.
export function queryRoute(method, path, callback) export function queryRoute(method, path, callback, type)
{ {
if (type) {
const ongoingRequest = ongoingRequests[type];
if (ongoingRequest) {
ongoingRequest.abort();
}
const navElement = document.getElementById(type + '-nav-link');
if (navElement) {
navElement.classList.add('progress');
}
}
const ajaxRequest = new XMLHttpRequest(); const ajaxRequest = new XMLHttpRequest();
ajaxRequest.onreadystatechange = function() { ajaxRequest.onreadystatechange = function() {
if (this.readyState === 4) { if (this.readyState === 4) {
if (type) {
if (ongoingRequests[type] !== ajaxRequest) {
return;
}
delete ongoingRequests[type];
const navElement = document.getElementById(type + '-nav-link');
if (navElement) {
navElement.classList.remove('progress');
}
}
const status = this.status; const status = this.status;
authError = status === 403; authError = status === 403;
switch (status) { switch (status) {
@ -30,6 +52,9 @@ export function queryRoute(method, path, callback)
} }
ajaxRequest.open(...args); ajaxRequest.open(...args);
ajaxRequest.send(); ajaxRequest.send();
if (type) {
ongoingRequests[type] = ajaxRequest;
}
return ajaxRequest; return ajaxRequest;
} }
@ -44,8 +69,9 @@ export function startFormQueryEx(formId, handler)
{ {
const form = document.getElementById(formId); const form = document.getElementById(formId);
const params = makeFormQueryParameter(form); const params = makeFormQueryParameter(form);
const queryType = formId.endsWith('-form') ? formId.substr(0, formId.length - 5) : formId;
return { return {
ajaxRequest: queryRoute(form.method, form.getAttribute('action') + params, handler), ajaxRequest: queryRoute(form.method, form.getAttribute('action') + params, handler, queryType),
form: form, form: form,
params, params, params, params,
}; };

View File

@ -34,13 +34,13 @@ export function initBuildActionsForm()
function queryBuildActions() function queryBuildActions()
{ {
AjaxHelper.queryRoute('GET', '/build-action', showBuildActions); AjaxHelper.queryRoute('GET', '/build-action', showBuildActions, 'build-action');
return true; return true;
} }
function queryBuildActionDetails(ids) function queryBuildActionDetails(ids)
{ {
AjaxHelper.queryRoute('GET', '/build-action/details?' + AjaxHelper.makeIdParams(ids), showBuildActionDetails); AjaxHelper.queryRoute('GET', '/build-action/details?' + AjaxHelper.makeIdParams(ids), showBuildActionDetails, 'build-action-details');
return true; return true;
} }

View File

@ -8,7 +8,7 @@ const status = {repoNames: undefined};
export function queryGlobalStatus() export function queryGlobalStatus()
{ {
AjaxHelper.queryRoute('GET', '/status', handleGlobalStatusUpdate); AjaxHelper.queryRoute('GET', '/status', handleGlobalStatusUpdate, 'global');
return true; return true;
} }

View File

@ -25,7 +25,7 @@ export function initPackageDetails(sectionElement, sectionData, newPackages)
}; };
AjaxHelper.queryRoute('GET', '/packages?details=1&name=' + encodeURIComponent(packageStr), function(ajaxRequest) { AjaxHelper.queryRoute('GET', '/packages?details=1&name=' + encodeURIComponent(packageStr), function(ajaxRequest) {
showPackageDetails(ajaxRequest, packageObj); showPackageDetails(ajaxRequest, packageObj);
}); }, 'package-details');
return true; return true;
} }
@ -38,7 +38,7 @@ export function queryPackageDetails(value, row)
{ {
AjaxHelper.queryRoute('GET', '/packages?details=1&name=' + encodeURIComponent(makePackageID(row)), function(ajaxRequest) { AjaxHelper.queryRoute('GET', '/packages?details=1&name=' + encodeURIComponent(makePackageID(row)), function(ajaxRequest) {
showPackageDetails(ajaxRequest, row); showPackageDetails(ajaxRequest, row);
}); }, 'package-details');
} }
function switchToPackageDetails(packageID) function switchToPackageDetails(packageID)