etiquette/frontends/etiquette_flask/static/js/spinner.js

176 lines
5.7 KiB
JavaScript
Raw Normal View History

var spinner = {};
spinner.Spinner =
function Spinner(element)
{
this.show = function(delay)
{
clearTimeout(this.delayed_showing_timeout);
this.delayed_showing_timeout = null;
if (delay)
{
this.delayed_showing_timeout = setTimeout(function(thisthis){thisthis.show()}, delay, this);
}
else
{
this.element.classList.remove("hidden");
}
}
this.hide = function()
{
clearTimeout(this.delayed_showing_timeout);
this.delayed_showing_timeout = null;
this.element.classList.add("hidden");
}
this.delayed_showing_timeout = null;
this.element = element;
}
spinner.spinner_button_index = 0;
spinner.button_spinner_groups = {};
// When a group member is closing, it will call the closer on all other members
// in the group. Of course, this would recurse forever without some kind of
// flagging, so this dict will hold group_id:true if a close is in progress,
// and be empty otherwise.
spinner.spinner_group_closing = {};
spinner.add_to_spinner_group =
function add_to_spinner_group(group_id, button)
{
if (!(group_id in spinner.button_spinner_groups))
{
spinner.button_spinner_groups[group_id] = [];
}
spinner.button_spinner_groups[group_id].push(button);
}
spinner.close_grouped_spinners =
function close_grouped_spinners(group_id)
{
if (group_id && !(spinner.spinner_group_closing[group_id]))
{
spinner.spinner_group_closing[group_id] = true;
for (const button of spinner.button_spinner_groups[group_id])
{
window[button.dataset.spinnerCloser]();
}
delete spinner.spinner_group_closing[group_id];
}
}
spinner.open_grouped_spinners =
function open_grouped_spinners(group_id)
{
for (const button of spinner.button_spinner_groups[group_id])
{
window[button.dataset.spinnerOpener]();
}
}
spinner.init_button_with_spinner =
function init_button_with_spinner()
{
/*
To create a button that has a spinner, and cannot be clicked again while
the action is running, assign it the class "button_with_spinner".
When you're ready for the spinner to disappear, call
window[button.dataset.spinnerCloser]().
Optional:
data-spinner-id: If you want to use your own element as the spinner,
give its ID here. Otherwise a new one will be created.
data-spinner-delay: The number of milliseconds to wait before the
spinner appears. For tasks that you expect to run very quickly,
this helps prevent a pointlessly short spinner. Note that the button
always becomes disabled immediately, and this delay only affects
the separate spinner element.
data-holder-class: CSS class for the new span that holds the menu.
data-spinner-group: An opaque string. All button_with_spinner that have
the same group will go into spinner mode when any of them is
clicked. Useful if you want to have two copies of a button on the
page, or two buttons which do opposite things and you only want one
to run at a time.
*/
let buttons = Array.from(document.getElementsByClassName("button_with_spinner"));
for (const button of buttons)
{
button.classList.remove("button_with_spinner");
button.innerHTML = button.innerHTML.trim();
let holder = document.createElement("span");
holder.classList.add("spinner_holder");
holder.classList.add(button.dataset.holderClass || "spinner_holder");
button.parentElement.insertBefore(holder, button);
holder.appendChild(button);
let spinner_element;
if (button.dataset.spinnerId)
{
spinner_element = document.getElementById(button.dataset.spinnerId);
}
else
{
spinner_element = document.createElement("span");
spinner_element.innerText = "Working...";
spinner_element.classList.add("hidden");
holder.appendChild(spinner_element);
}
if (button.dataset.spinnerGroup)
{
spinner.add_to_spinner_group(button.dataset.spinnerGroup, button);
}
let spin = new spinner.Spinner(spinner_element);
let spin_delay = parseFloat(button.dataset.spinnerDelay) || 0;
button.dataset.spinnerOpener = "spinner_opener_" + spinner.spinner_button_index;
window[button.dataset.spinnerOpener] = function spinner_opener()
{
spin.show(spin_delay);
button.disabled = true;
}
// It is expected that the function referenced by onclick will call
// window[button.dataset.spinnerCloser]() when appropriate, since from
// our perspective we cannot be sure when to close the spinner.
button.dataset.spinnerCloser = "spinner_closer_" + spinner.spinner_button_index;
window[button.dataset.spinnerCloser] = function spinner_closer()
{
spinner.close_grouped_spinners(button.dataset.spinnerGroup);
spin.hide();
button.disabled = false;
}
let wrapped_onclick = button.onclick;
button.removeAttribute('onclick');
button.onclick = function()
{
if (button.dataset.spinnerGroup)
{
spinner.open_grouped_spinners(button.dataset.spinnerGroup);
}
else
{
window[button.dataset.spinnerOpener]();
}
return wrapped_onclick();
}
spinner.spinner_button_index += 1;
}
}
spinner.on_pageload =
function on_pageload()
{
spinner.init_button_with_spinner();
}
document.addEventListener("DOMContentLoaded", spinner.on_pageload);