demarches-normaliennes/app/javascript/shared/utils.ts

210 lines
5.1 KiB
TypeScript
Raw Normal View History

import Rails from '@rails/ujs';
2018-10-09 11:32:27 +02:00
import debounce from 'debounce';
export { debounce };
2020-10-08 11:19:16 +02:00
export const { fire, csrfToken } = Rails;
2018-10-09 11:32:27 +02:00
2022-02-02 17:16:50 +01:00
export function show(el: HTMLElement) {
el && el.classList.remove('hidden');
2018-09-06 19:23:27 +02:00
}
2022-02-02 17:16:50 +01:00
export function hide(el: HTMLElement) {
el && el.classList.add('hidden');
2018-09-06 19:23:27 +02:00
}
2022-02-02 17:16:50 +01:00
export function toggle(el: HTMLElement, force?: boolean) {
if (force == undefined) {
2022-02-02 17:16:50 +01:00
el && el.classList.toggle('hidden');
} else if (force) {
el && el.classList.remove('hidden');
} else {
el && el.classList.add('hidden');
}
2018-09-06 19:23:27 +02:00
}
2018-10-09 11:32:27 +02:00
2022-02-02 17:16:50 +01:00
export function enable(el: HTMLInputElement) {
2019-11-19 17:55:30 +01:00
el && (el.disabled = false);
}
2022-02-02 17:16:50 +01:00
export function disable(el: HTMLInputElement) {
2019-11-19 17:55:30 +01:00
el && (el.disabled = true);
}
2022-02-02 17:16:50 +01:00
export function hasClass(el: HTMLElement, cssClass: string) {
2019-11-19 17:55:30 +01:00
return el && el.classList.contains(cssClass);
}
2022-02-02 17:16:50 +01:00
export function addClass(el: HTMLElement, cssClass: string) {
2019-11-19 17:55:30 +01:00
el && el.classList.add(cssClass);
}
2022-02-02 17:16:50 +01:00
export function removeClass(el: HTMLElement, cssClass: string) {
2019-11-19 17:55:30 +01:00
el && el.classList.remove(cssClass);
}
2022-02-02 17:16:50 +01:00
export function delegate(
eventNames: string,
selector: string,
callback: () => void
) {
2018-10-09 11:32:27 +02:00
eventNames
.split(' ')
2020-04-30 15:42:29 +02:00
.forEach((eventName) =>
2018-10-09 11:32:27 +02:00
Rails.delegate(document, selector, eventName, callback)
);
}
2021-07-20 17:59:55 +02:00
// A promise-based wrapper for Rails.ajax().
//
// Returns a Promise that is either:
// - resolved in case of a 20* HTTP response code,
// - rejected with an Error object otherwise.
//
// See Rails.ajax() code for more details.
2022-02-02 17:16:50 +01:00
export function ajax(options: Rails.AjaxOptions) {
return new Promise((resolve, reject) => {
Object.assign(options, {
2022-02-02 17:16:50 +01:00
success: (
response: unknown,
statusText: string,
xhr: { status: number }
) => {
resolve({ response, statusText, xhr });
},
2022-02-02 17:16:50 +01:00
error: (
response: unknown,
statusText: string,
xhr: { status: number }
) => {
// NB: on HTTP/2 connections, statusText is always empty.
2022-02-02 17:16:50 +01:00
const error = new Error(
`Erreur ${xhr.status}` + (statusText ? ` : ${statusText}` : '')
);
Object.assign(error, { response, statusText, xhr });
reject(error);
}
});
Rails.ajax(options);
});
}
class ResponseError extends Error {
response: Response;
constructor(response: Response) {
super(String(response.statusText || response.status));
this.response = response;
}
}
2022-02-02 17:16:50 +01:00
export function getJSON(url: string, data: unknown, method = 'GET') {
2020-10-08 11:19:16 +02:00
const { query, ...options } = fetchOptions(data, method);
return fetch(`${url}${query}`, options).then((response) => {
if (response.ok) {
if (response.status === 204) {
return null;
}
return response.json();
}
throw new ResponseError(response);
2020-10-08 11:19:16 +02:00
});
2018-10-09 11:32:27 +02:00
}
export function scrollTo(container: HTMLElement, scrollTo: HTMLElement) {
2018-10-09 11:43:38 +02:00
container.scrollTop =
offset(scrollTo).top - offset(container).top + container.scrollTop;
2018-10-09 11:32:27 +02:00
}
2022-02-02 17:16:50 +01:00
export function scrollToBottom(container: HTMLElement) {
2018-10-09 11:43:38 +02:00
container.scrollTop = container.scrollHeight;
2018-10-09 11:32:27 +02:00
}
2022-02-02 17:16:50 +01:00
export function on(
selector: string,
eventName: string,
fn: (event: Event, detail: unknown) => void
) {
2020-04-30 15:42:29 +02:00
[...document.querySelectorAll(selector)].forEach((element) =>
2022-02-02 17:16:50 +01:00
element.addEventListener(eventName, (event) =>
fn(event, (event as CustomEvent).detail)
)
2018-10-09 11:32:27 +02:00
);
}
2018-10-09 11:43:38 +02:00
2022-02-23 13:07:23 +01:00
export function isNumeric(s: string) {
const n = parseFloat(s);
return !isNaN(n) && isFinite(n);
2020-01-23 15:12:19 +01:00
}
2022-02-02 17:16:50 +01:00
function offset(element: HTMLElement) {
2018-10-09 11:43:38 +02:00
const rect = element.getBoundingClientRect();
return {
top: rect.top + document.body.scrollTop,
left: rect.left + document.body.scrollLeft
};
}
// Takes a promise, and return a promise that times out after the given delay.
2022-02-02 17:16:50 +01:00
export function timeoutable<T>(
promise: Promise<T>,
timeoutDelay: number
): Promise<T> {
const timeoutPromise = new Promise<T>((resolve, reject) => {
setTimeout(() => {
reject(new Error(`Promise timed out after ${timeoutDelay}ms`));
}, timeoutDelay);
});
return Promise.race([promise, timeoutPromise]);
}
2020-10-08 11:19:16 +02:00
const FETCH_TIMEOUT = 30 * 1000; // 30 sec
2022-02-02 17:16:50 +01:00
function fetchOptions(data: unknown, method = 'GET') {
const options: RequestInit & {
query: string;
headers: Record<string, string>;
} = {
2020-10-08 11:19:16 +02:00
query: '',
method: method.toUpperCase(),
headers: {
accept: 'application/json',
2022-02-02 17:16:50 +01:00
'x-csrf-token': csrfToken() ?? '',
2020-10-08 11:19:16 +02:00
'x-requested-with': 'XMLHttpRequest'
},
credentials: 'same-origin'
};
if (data) {
if (options.method === 'GET') {
2022-02-02 17:16:50 +01:00
options.query = objectToQuerystring(data as Record<string, string>);
2020-10-08 11:19:16 +02:00
} else {
options.headers['content-type'] = 'application/json';
options.body = JSON.stringify(data);
}
}
if (window.AbortController) {
const controller = new AbortController();
options.signal = controller.signal;
setTimeout(() => {
controller.abort();
}, FETCH_TIMEOUT);
}
return options;
}
2022-02-02 17:16:50 +01:00
function objectToQuerystring(obj: Record<string, string>): string {
2020-10-08 11:19:16 +02:00
return Object.keys(obj).reduce(function (query, key, i) {
return [
query,
i === 0 ? '?' : '&',
encodeURIComponent(key),
'=',
encodeURIComponent(obj[key])
].join('');
}, '');
}