1203 lines
29 KiB
JavaScript
1203 lines
29 KiB
JavaScript
(function () {
|
||
let d = document.documentElement;
|
||
window.g = window.garradin = {
|
||
admin_url: d.dataset.url,
|
||
static_url: d.dataset.url + 'static/',
|
||
version: d.dataset.version,
|
||
loaded: {}
|
||
};
|
||
|
||
d.classList.remove('nojs');
|
||
d.classList.add('js');
|
||
|
||
window.$ = function(selector) {
|
||
if (!selector.match(/^[.#]?[a-z0-9_-]+$/i))
|
||
{
|
||
return document.querySelectorAll(selector);
|
||
}
|
||
else if (selector.substr(0, 1) == '.')
|
||
{
|
||
return document.getElementsByClassName(selector.substr(1));
|
||
}
|
||
else if (selector.substr(0, 1) == '#')
|
||
{
|
||
return document.getElementById(selector.substr(1));
|
||
}
|
||
else
|
||
{
|
||
return document.getElementsByTagName(selector);
|
||
}
|
||
};
|
||
|
||
if (!document.querySelectorAll)
|
||
{
|
||
return;
|
||
}
|
||
|
||
g.onload = function(callback, dom)
|
||
{
|
||
if (typeof dom === 'undefined') {
|
||
dom = true;
|
||
}
|
||
|
||
var eventName = dom ? 'DOMContentLoaded' : 'load';
|
||
|
||
window.addEventListener(eventName, callback);
|
||
|
||
if (!dom && document.readyState === 'complete') {
|
||
callback();
|
||
}
|
||
};
|
||
|
||
g.toggle = function(selector, visibility, resize_parent)
|
||
{
|
||
if (!('classList' in document.documentElement))
|
||
return false;
|
||
|
||
if (selector instanceof Array)
|
||
{
|
||
for (var i = 0; i < selector.length; i++)
|
||
{
|
||
g.toggle(selector[i], visibility, false);
|
||
}
|
||
|
||
if (resize_parent !== false) {
|
||
g.resizeParentDialog();
|
||
}
|
||
|
||
return true;
|
||
}
|
||
else if (selector instanceof HTMLElement) {
|
||
var elements = [selector];
|
||
}
|
||
else {
|
||
var elements = document.querySelectorAll(selector);
|
||
}
|
||
|
||
for (var i = 0; i < elements.length; i++) {
|
||
elements[i].classList.toggle('hidden', visibility ? false : true);
|
||
|
||
elements[i].querySelectorAll('[data-required]').forEach(e => {
|
||
e.required = parseInt(e.dataset.required, 10);
|
||
});
|
||
|
||
// Make sure hidden elements are not really required
|
||
// Avoid Chrome bug "An invalid form control with name='' is not focusable."
|
||
elements[i].querySelectorAll('input[required], textarea[required], select[required], button[required]').forEach((e) => {
|
||
if (typeof e.dataset.disabled === 'undefined') {
|
||
e.dataset.disabled = e.hasAttribute('disabled') ? 1 : 0;
|
||
}
|
||
|
||
e.disabled = !visibility ? true : parseInt(e.dataset.disabled, 10);
|
||
});
|
||
}
|
||
|
||
if (resize_parent !== false) {
|
||
g.resizeParentDialog();
|
||
}
|
||
|
||
return true;
|
||
};
|
||
|
||
g.script = function (file, callback) {
|
||
if (file in g.loaded) {
|
||
callback();
|
||
return;
|
||
}
|
||
|
||
if (!file.match(/^https?:\/\//)) {
|
||
file = this.static_url + file + '?' + g.version;
|
||
}
|
||
|
||
var script = g.loaded[file] = document.createElement('script');
|
||
script.type = 'text/javascript';
|
||
script.src = file;
|
||
script.onload = callback;
|
||
document.head.appendChild(script);
|
||
};
|
||
|
||
g.style = function (file) {
|
||
var link = document.createElement('link');
|
||
link.rel = 'stylesheet';
|
||
link.type = 'text/css';
|
||
link.href = this.static_url + file + '?' + g.version;
|
||
return document.head.appendChild(link);
|
||
};
|
||
|
||
g.normalizeString = function (str) {
|
||
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
||
};
|
||
|
||
g.dialog = null;
|
||
g.dialog_title = null;
|
||
g.focus_before_dialog = null;
|
||
g.dialog_on_close = false;
|
||
g.dialog_options = null;
|
||
|
||
g.openDialog = function (content, options) {
|
||
if (null !== g.dialog) {
|
||
g.closeDialog();
|
||
}
|
||
return g.replaceDialog(content, options);
|
||
};
|
||
|
||
g.createDialog = function (content, options) {
|
||
options = g.getDialogOptions(options);
|
||
g.dialog_options = options;
|
||
|
||
g.focus_before_dialog = document.activeElement;
|
||
|
||
g.dialog = document.createElement('dialog');
|
||
g.dialog.id = 'dialog';
|
||
g.dialog.className = options.classname || '';
|
||
g.dialog.dataset.caption = options.caption || '';
|
||
|
||
var toolbar = document.createElement('header');
|
||
toolbar.className = 'toolbar';
|
||
|
||
var t = document.createElement('h4');
|
||
t.className = 'title';
|
||
toolbar.appendChild(t);
|
||
|
||
g.dialog_title = document.title;
|
||
|
||
if (options.close) {
|
||
var btn = document.createElement('button');
|
||
btn.className = 'icn-btn closeBtn main';
|
||
btn.setAttribute('data-icon', '✘');
|
||
btn.type = 'button';
|
||
btn.innerHTML = 'Fermer';
|
||
btn.onclick = g.closeDialog;
|
||
toolbar.appendChild(btn);
|
||
}
|
||
|
||
g.dialog.style.opacity = 0;
|
||
g.dialog.appendChild(toolbar);
|
||
document.body.appendChild(g.dialog);
|
||
g.dialog.showModal();
|
||
|
||
// Remove ability to scoll background when dialog is open
|
||
// Avoid having the page "jumping" because the scrollbar has been removed
|
||
document.body.style.width = document.body.clientWidth + 'px';
|
||
document.body.style.overflow = 'hidden';
|
||
};
|
||
|
||
g.getDialogOptions = function (options) {
|
||
if (typeof options !== 'object' || options === null) {
|
||
options = {};
|
||
}
|
||
|
||
options.callback = options.callback ?? null;
|
||
options.classname = options.classname ?? null;
|
||
options.close = options.close ?? true;
|
||
options.caption = options.caption ?? null;
|
||
options.click_to_close = options.click_to_close ?? false;
|
||
options.escape_to_close = options.escape_to_close ?? true;
|
||
g.dialog_on_close = options.on_close || false;
|
||
return options;
|
||
};
|
||
|
||
g.replaceDialog = function (content, options) {
|
||
if (!g.dialog) {
|
||
g.createDialog(content, options);
|
||
}
|
||
else {
|
||
g.dialog.style.opacity = 0;
|
||
g.dialog.classList.remove('loaded');
|
||
g.dialog.lastElementChild.remove();
|
||
g.resetDialogEvents();
|
||
}
|
||
|
||
options = g.getDialogOptions(options);
|
||
|
||
var t = g.dialog.querySelector('h4.title');
|
||
|
||
if (!t) {
|
||
return;
|
||
}
|
||
|
||
t.innerText = options.caption || '';
|
||
|
||
if (options.caption && !document.title.match(options.caption)) {
|
||
document.title = options.caption + ' — ' + g.dialog_title;
|
||
}
|
||
|
||
g.setDialogKey('Escape', g.closeDialogOnEscape);
|
||
|
||
if (typeof content == 'string') {
|
||
var container = document.createElement('div');
|
||
container.className = 'content';
|
||
container.innerHTML = content;
|
||
content = container;
|
||
}
|
||
else if (content instanceof DocumentFragment) {
|
||
var container = document.createElement('div');
|
||
container.className = 'content';
|
||
container.appendChild(content.cloneNode(true));
|
||
content = container;
|
||
}
|
||
|
||
let tag = content.tagName.toLowerCase();
|
||
|
||
if (tag !== 'iframe' && tag !== 'div') {
|
||
var container = document.createElement('div');
|
||
container.className = 'preview';
|
||
container.appendChild(content);
|
||
g.dialog.appendChild(container);
|
||
}
|
||
else {
|
||
g.dialog.appendChild(content);
|
||
}
|
||
|
||
if (tag == 'img' || tag == 'iframe') {
|
||
event = 'load';
|
||
}
|
||
else if (tag == 'audio' || tag == 'video') {
|
||
event = 'canplaythrough';
|
||
}
|
||
|
||
if (event) {
|
||
content.addEventListener(event, () => { if (g.dialog) g.dialog.classList.add('loaded'); });
|
||
|
||
if (event && options.callback) {
|
||
content.addEventListener(event, options.callback);
|
||
}
|
||
}
|
||
else {
|
||
g.dialog.classList.add('loaded');
|
||
}
|
||
|
||
// Restore CSS defaults
|
||
window.setTimeout(() => { g.dialog.style.opacity = ''; }, 50);
|
||
|
||
if (options.click_to_close) {
|
||
g.dialog.onclick = (e) => {
|
||
if (e.target === g.dialog) {
|
||
g.closeDialog();
|
||
}
|
||
};
|
||
}
|
||
|
||
return content;
|
||
};
|
||
|
||
g.openFrameDialog = function (url, options) {
|
||
options = options ?? {};
|
||
options.height = options.height || 'auto';
|
||
options.callback = options.callback || null;
|
||
options.classname = options.classname || null;
|
||
options.escape_to_close = options.escape_to_close || true;
|
||
|
||
var iframe = document.createElement('iframe');
|
||
iframe.src = url;
|
||
iframe.name = 'dialog';
|
||
iframe.id = 'frameDialog';
|
||
iframe.frameborder = '0';
|
||
iframe.scrolling = 'yes';
|
||
iframe.width = iframe.height = 0;
|
||
iframe.setAttribute('data-height', options.height);
|
||
|
||
iframe.addEventListener('load', () => {
|
||
iframe.contentWindow.onkeyup = (e) => { if (e.key === 'Escape') g.closeDialogOnEscape(); };
|
||
|
||
if (iframe.parentNode.className) {
|
||
return;
|
||
}
|
||
|
||
// We need to wait a bit for the height to be correct, not sure why
|
||
window.setTimeout(() => {
|
||
var height = iframe.dataset.height;
|
||
|
||
if (iframe.dataset.height == 'auto'
|
||
&& iframe.contentWindow.document.body
|
||
&& iframe.contentWindow.document.body.offsetHeight < g.dialog.offsetHeight) {
|
||
height = iframe.contentWindow.document.body.offsetHeight + 'px';
|
||
}
|
||
else if (!height || height == 'auto') {
|
||
height = '90%';
|
||
}
|
||
|
||
iframe.style.height = height;
|
||
}, 200);
|
||
});
|
||
|
||
g.openDialog(iframe, options);
|
||
return iframe;
|
||
};
|
||
|
||
g.reloadParentDialog = () => {
|
||
if (typeof window.parent.g === 'undefined' || !window.parent.g.dialog) {
|
||
return;
|
||
}
|
||
|
||
location.href = window.parent.g.dialog.querySelector('iframe').getAttribute('src');
|
||
};
|
||
|
||
g.setParentDialogHeight = (height) => {
|
||
if (typeof window.parent.g === 'undefined' || !window.parent.g.dialog) {
|
||
return;
|
||
}
|
||
|
||
window.parent.g.dialog.querySelector('iframe').setAttribute('data-height', height);
|
||
g.resizeParentDialog(height);
|
||
};
|
||
|
||
g.toggleDialogFullscreen = () => {
|
||
g.dialog.classList.add('fullscreen')
|
||
g.dialog.childNodes[1].style.height = null;
|
||
};
|
||
|
||
g.resizeParentDialog = (forced_height) => {
|
||
if (typeof window.parent.g === 'undefined' || !window.parent.g.dialog) {
|
||
return;
|
||
}
|
||
|
||
var p = window.parent;
|
||
var dialog = p.g.dialog;
|
||
|
||
if (dialog.classList.contains('fullscreen')) {
|
||
return;
|
||
}
|
||
|
||
if (!dialog.dataset.caption && document.title) {
|
||
var title = document.title.replace(/^(\s[^—-]+\s).*$/, "$1");
|
||
dialog.querySelector('.title').innerText = title;
|
||
p.document.title = document.title + ' — ' + p.g.dialog_title;
|
||
}
|
||
|
||
let height;
|
||
|
||
if (forced_height) {
|
||
height = forced_height;
|
||
}
|
||
else {
|
||
let body_height = document.body.offsetHeight;
|
||
let parent_height = p.innerHeight;
|
||
|
||
if (body_height > parent_height * 0.9) {
|
||
height = '90%';
|
||
}
|
||
else {
|
||
height = body_height + 'px';
|
||
}
|
||
}
|
||
|
||
dialog.childNodes[1].style.height = height;
|
||
};
|
||
|
||
g.closeDialogOnEscape = () => {
|
||
if (!g.dialog_options.escape_to_close) {
|
||
return true;
|
||
}
|
||
|
||
g.closeDialog();
|
||
return false;
|
||
};
|
||
|
||
g.closeDialog = function () {
|
||
for (var i in g.dialog_events) {
|
||
if (!g.dialog_events.hasOwnProperty(i)) {
|
||
continue;
|
||
}
|
||
|
||
var evt = g.dialog_events[i];
|
||
if (evt[0] !== 'close') {
|
||
continue;
|
||
}
|
||
|
||
evt[1]();
|
||
}
|
||
|
||
g.resetDialogEvents();
|
||
|
||
if (null === g.dialog) {
|
||
return;
|
||
}
|
||
|
||
if (g.dialog.preventClose && g.dialog.preventClose()) {
|
||
return false;
|
||
}
|
||
|
||
if (g.dialog_on_close) {
|
||
location.href = g.dialog_on_close == true ? location.href : g.dialog_on_close.replace(/!/, g.admin_url);
|
||
return;
|
||
}
|
||
|
||
var d = g.dialog;
|
||
d.style.opacity = 0;
|
||
g.dialog = null;
|
||
g.dialog_options = null;
|
||
|
||
window.setTimeout(() => { d.parentNode.removeChild(d); }, 500);
|
||
|
||
if (g.focus_before_dialog) {
|
||
g.focus_before_dialog.focus();
|
||
}
|
||
|
||
if (g.dialog_title !== null) {
|
||
document.title = g.dialog_title;
|
||
g.dialog_title = null;
|
||
}
|
||
|
||
document.body.style.overflow = null;
|
||
document.body.style.width = null;
|
||
};
|
||
|
||
g.openFormInDialog = (form) => {
|
||
if (form.target != '_dialog' && form.target != 'dialog') {
|
||
return;
|
||
}
|
||
|
||
let url = form.getAttribute('action');
|
||
url = url + (url.indexOf('?') > 0 ? '&' : '?') + '_dialog';
|
||
form.setAttribute('action', url);
|
||
form.target = 'dialog';
|
||
|
||
g.openFrameDialog('about:blank', {'height': form.getAttribute('data-dialog-height') ? 90 : 'auto'});
|
||
form.submit();
|
||
return false;
|
||
};
|
||
|
||
g.checkUncheck = function()
|
||
{
|
||
var checked = this.checked;
|
||
this.form.querySelectorAll('tbody input[type=checkbox]').forEach((elm) => {
|
||
elm.checked = checked;
|
||
elm.dispatchEvent(new Event("change"));
|
||
});
|
||
|
||
this.form.querySelectorAll('thead input[type=checkbox], tfoot input[type=checkbox]').forEach((elm) => {
|
||
elm.checked = checked;
|
||
});
|
||
|
||
return true;
|
||
};
|
||
|
||
g.togglePasswordVisibility = (field, repeat, show) => {
|
||
if (typeof show == 'undefined') {
|
||
show = field.type.toLowerCase() == 'password';
|
||
}
|
||
|
||
var btn = field.nextSibling;
|
||
|
||
if (!btn) {
|
||
throw Error('button not found');
|
||
}
|
||
|
||
field.type = show ? 'text' : 'password';
|
||
btn.dataset.icon = !show ? '👁' : '⤫';
|
||
btn.innerHTML = !show ? 'Voir le mot de passe' : 'Cacher le mot de passe';
|
||
field.classList.toggle('clearTextPassword', !show);
|
||
|
||
if (repeat) {
|
||
repeat.type = field.type;
|
||
repeat.classList.toggle('clearTextPassword', !show);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Adds a "show password" button next to password inputs
|
||
*/
|
||
g.enhancePasswordField = function (field)
|
||
{
|
||
if (field.id.indexOf('_confirmed') != -1) {
|
||
return;
|
||
}
|
||
|
||
var show_password = document.createElement('button');
|
||
show_password.type = 'button';
|
||
show_password.className = 'icn-btn';
|
||
|
||
field.parentNode.insertBefore(show_password, field.nextSibling);
|
||
|
||
let repeat_field = document.getElementById(field.id + '_confirmed');
|
||
|
||
g.togglePasswordVisibility(field, repeat_field, false);
|
||
|
||
show_password.onclick = function (e) {
|
||
var pos = field.selectionStart;
|
||
|
||
g.togglePasswordVisibility(field, repeat_field);
|
||
|
||
// Remettre le focus sur le champ mot de passe
|
||
// on ne peut pas vraiment remettre le focus sur le champ
|
||
// précis qui était utilisé avant de cliquer sur le bouton
|
||
// car il faudrait enregistrer les actions onfocus de tous
|
||
// les champs de la page
|
||
field.focus();
|
||
field.selectionStart = field.selectionEnd = pos;
|
||
};
|
||
};
|
||
|
||
g.enhanceHueField = (input) => {
|
||
var container = document.createElement('span');
|
||
container.className = 'hue-selector';
|
||
|
||
var c = input.cloneNode(true);
|
||
var gradient = document.createElement('span');
|
||
gradient.className = 'gradient';
|
||
|
||
if (c.dataset.grey) {
|
||
var grey = document.createElement('span');
|
||
grey.className = 'grey';
|
||
gradient.appendChild(grey);
|
||
}
|
||
|
||
var color = document.createElement('span');
|
||
color.className = 'color';
|
||
gradient.appendChild(color);
|
||
|
||
var handle = document.createElement('span');
|
||
handle.className = 'handle';
|
||
|
||
container.appendChild(gradient);
|
||
container.appendChild(handle);
|
||
container.appendChild(c);
|
||
|
||
var s = c.dataset.saturation ?? '100%';
|
||
var l = c.dataset.lightness ?? '50%';
|
||
|
||
container.style = '--sl: ' + s + ', ' + l;
|
||
|
||
var range = 360;
|
||
|
||
// Add 60 shades of grey
|
||
if (c.dataset.grey) {
|
||
container.className += ' hue-grey';
|
||
c.min = '-60';
|
||
range += 60;
|
||
}
|
||
|
||
var updateHandle = () => {
|
||
var v = parseInt(c.value, 10) || 0;
|
||
|
||
if (v > -10 && v < 0) {
|
||
v = -1; // White
|
||
}
|
||
else if (v < -50) {
|
||
v = -60; // Black
|
||
}
|
||
|
||
if (v >= 0) {
|
||
var cs = s,
|
||
cl = l,
|
||
h = c.value,
|
||
pos = (c.dataset.grey ? 60 + v : v) / range;
|
||
}
|
||
// For grey colors
|
||
else {
|
||
var cs = '0%',
|
||
cl = (Math.abs(v) / 60) * 100 + '%',
|
||
h = 0,
|
||
pos = (v + 60) / range;
|
||
}
|
||
|
||
c.value = v;
|
||
pos *= 100;
|
||
var hsl = h + ', ' + cs + ', ' + cl;
|
||
|
||
handle.style = '--position: ' + pos + '%; --hsl: hsl(' + hsl + ')';
|
||
};
|
||
|
||
c.addEventListener('input', updateHandle);
|
||
updateHandle();
|
||
|
||
c.addEventListener('focus', () => container.classList.add('focus'));
|
||
c.addEventListener('blur', () => container.classList.remove('focus'));
|
||
|
||
input.replaceWith(container);
|
||
};
|
||
|
||
g.enhanceDateField = (input) => {
|
||
var span = document.createElement('span');
|
||
span.className = 'datepicker-parent';
|
||
var btn = document.createElement('button');
|
||
var cal = null;
|
||
btn.className = 'datepicker icn-btn';
|
||
btn.title = 'Cliquer pour ouvrir le calendrier. Utiliser les flèches du clavier pour sélectionner une date, et page précédente suivante pour changer de mois.';
|
||
btn.setAttribute('data-icon', '📅');
|
||
btn.type = 'button';
|
||
btn.onclick = () => {
|
||
g.script('scripts/lib/datepicker2.min.js', () => {
|
||
if (null == cal) {
|
||
btn.onclick = null;
|
||
cal = new DatePicker(btn, input, {lang: 'fr', format: 1});
|
||
cal.open();
|
||
}
|
||
});
|
||
};
|
||
span.appendChild(btn);
|
||
input.parentNode.insertBefore(span, input.nextSibling);
|
||
|
||
const getCaretPosition = e => e && e.selectionStart || -1;
|
||
|
||
const inputKeyEvent = (e) => {
|
||
if (input.value.match(/^\d$|^\d\d?\/\d$/) && e.key.match(/^[0-9]$/)) {
|
||
input.value += e.key + '/';
|
||
e.preventDefault();
|
||
return false;
|
||
}
|
||
|
||
if (e.key == '/' && input.value.slice(-1) == '/') {
|
||
e.preventDefault();
|
||
return false;
|
||
}
|
||
|
||
};
|
||
input.addEventListener('keydown', inputKeyEvent, true);
|
||
};
|
||
|
||
g.current_list_input = null;
|
||
|
||
g.inputListSelected = function(value, label) {
|
||
var i = g.current_list_input;
|
||
|
||
if (!i) {
|
||
throw Error('Parent input list not found');
|
||
}
|
||
|
||
var can_delete = i.firstChild.getAttribute('data-can-delete') == 1;
|
||
var multiple = i.firstChild.getAttribute('data-multiple') == 1;
|
||
var name = i.firstChild.getAttribute('data-name');
|
||
|
||
var span = document.createElement('span');
|
||
span.className = 'label';
|
||
|
||
var input = document.createElement('input');
|
||
input.type = 'hidden';
|
||
input.name = name + '[' + value + ']';
|
||
input.value = label;
|
||
|
||
span.appendChild(input);
|
||
span.appendChild(document.createTextNode(label));
|
||
|
||
// Add delete button
|
||
if (can_delete == 1) {
|
||
var btn = document.createElement('button');
|
||
btn.className = 'icn-btn';
|
||
btn.type = 'button';
|
||
btn.setAttribute('data-icon', '✘');
|
||
btn.onclick = () => span.parentNode.removeChild(span);
|
||
span.appendChild(btn);
|
||
}
|
||
|
||
if (!multiple && (old = i.querySelector('span'))) {
|
||
i.removeChild(old);
|
||
}
|
||
|
||
i.appendChild(span);
|
||
g.closeDialog();
|
||
i.firstChild.focus();
|
||
};
|
||
|
||
g.formatMoney = (v, html) => {
|
||
if (!v) {
|
||
return '0,00';
|
||
}
|
||
|
||
var s = v < 0 ? '-' : '';
|
||
v = '' + Math.abs(v);
|
||
|
||
var units = v.substr(0, v.length-2) || '0';
|
||
|
||
if (html) {
|
||
// Add spacer
|
||
units = units.split("").reverse().join("");
|
||
units = units.replace(/(\d{3})/g, "$1\xa0");
|
||
units = units.split("").reverse().join("").trim("\xa0");
|
||
}
|
||
|
||
return s + units + ',' + ('00' + v).substr(-2);
|
||
};
|
||
|
||
g.getMoneyAsInt = (v) => {
|
||
v = v.replace(/[^0-9.,-]/, '');
|
||
if (v.length == 0) return;
|
||
var m = 1;
|
||
|
||
if (v.match(/^-/)) {
|
||
m = -1;
|
||
v = v.substr(1);
|
||
}
|
||
|
||
v = v.split(/[,.]/);
|
||
var d = v.length == 2 ? v[1] : '0';
|
||
v = v[0] + (d + '00').substr(0, 2);
|
||
v = parseInt(v, 10) * m;
|
||
return v;
|
||
};
|
||
|
||
// Focus on first form input when loading the page
|
||
g.onload(() => {
|
||
if (!document.activeElement || document.activeElement.tagName.toLowerCase() == 'body') {
|
||
let form = document.querySelector('form[data-focus]');
|
||
|
||
if (!form) {
|
||
return;
|
||
}
|
||
|
||
let f = form.dataset.focus;
|
||
let n = f.match(/^\d+$/) ? (parseInt(f, 10) - 1) : null;
|
||
let i = form.querySelectorAll(n !== null ? '[name]:not([type="hidden"]):not([readonly]):not([type=button])' : f);
|
||
var element;
|
||
|
||
if (n !== null && i[n]) {
|
||
element = i[n];
|
||
}
|
||
else if (n === null && i[0]) {
|
||
element = i[0];
|
||
}
|
||
|
||
// Don't focus inputs on touch screens as this opens the keyboard
|
||
if (!element || (element.tagName.toLowerCase() === 'input' && ('ontouchstart' in window))) {
|
||
return;
|
||
}
|
||
|
||
element.focus();
|
||
}
|
||
});
|
||
|
||
g.listSelectorButtonClicked = (e) => {
|
||
var i = e.target;
|
||
i.setCustomValidity('');
|
||
g.current_list_input = i.parentNode;
|
||
var max = i.getAttribute('data-max');
|
||
|
||
if (max > 0 && max <= i.parentNode.querySelectorAll('span').length) {
|
||
alert('Il n\'est pas possible de faire plus de ' + max + ' choix.');
|
||
return false;
|
||
}
|
||
|
||
let url = i.value + (i.value.indexOf('?') > 0 ? '&' : '?') + '_dialog';
|
||
var caption = i.dataset.caption || null;
|
||
g.openFrameDialog(url, {caption});
|
||
return false;
|
||
};
|
||
|
||
// List selectors, using an iframe for list
|
||
g.onload(() => {
|
||
var inputs = $('form .input-list > button');
|
||
|
||
inputs.forEach((i) => {
|
||
i.onclick = g.listSelectorButtonClicked;
|
||
});
|
||
|
||
// Set custom error message if required list is not selected
|
||
document.querySelectorAll('form').forEach((form) => {
|
||
let elements = form.elements;
|
||
|
||
// Make sure hidden or disabled form elements are not required
|
||
for (var j = 0; j < elements.length; j++) {
|
||
var element = elements[j];
|
||
|
||
if (element.required && (element.disabled || !element.offsetParent)) {
|
||
element.dataset.required = element.hasAttribute('required') ? 1 : 0;
|
||
element.required = false;
|
||
}
|
||
}
|
||
|
||
form.addEventListener('submit', (e) => {
|
||
let elements = form.elements;
|
||
|
||
// Make sure hidden or disabled form elements are not required
|
||
for (var j = 0; j < elements.length; j++) {
|
||
var element = elements[j];
|
||
|
||
if (element.disabled || !element.offsetParent) {
|
||
element.required = false;
|
||
}
|
||
}
|
||
|
||
let inputs = form.querySelectorAll('.input-list > button[required]');
|
||
|
||
for (var k = 0; k < inputs.length; k++) {
|
||
var i2 = inputs[k];
|
||
|
||
// Ignore hidden / disabled form elements
|
||
if (i2.disabled || !i2.offsetParent) {
|
||
continue;
|
||
}
|
||
|
||
let v = i2.parentNode.querySelector('input[type="hidden"]:nth-child(1)');
|
||
|
||
if (!v || !v.value) {
|
||
// Force button to have error message, <button type="button"> cannot show validity message
|
||
i2.type = 'submit';
|
||
i2.setCustomValidity('Merci de faire une sélection.');
|
||
i2.reportValidity();
|
||
e.preventDefault();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
});
|
||
});
|
||
|
||
var multiples = $('form .input-list span button');
|
||
|
||
multiples.forEach((btn) => {
|
||
btn.onclick = () => btn.parentNode.parentNode.removeChild(btn.parentNode);
|
||
});
|
||
|
||
// Open links in dialog
|
||
$('a[target*="_dialog"]').forEach((e) => {
|
||
e.onclick = () => g.openPreview(e);
|
||
});
|
||
|
||
$('form[target="_dialog"]').forEach((e) => {
|
||
e.addEventListener('submit', () => g.openFormInDialog(e));
|
||
});
|
||
});
|
||
|
||
g.dialog_events = [];
|
||
g.setDialogKey = function (key, callback) {
|
||
g.addDialogEvent('keyup', (e) => {
|
||
if (e.key !== key) {
|
||
return;
|
||
}
|
||
|
||
e.preventDefault();
|
||
callback();
|
||
return false;
|
||
});
|
||
|
||
if (key === 'ArrowLeft') {
|
||
g.addDialogEvent('swipeleft', callback);
|
||
}
|
||
else if (key === 'ArrowRight') {
|
||
g.addDialogEvent('swiperight', callback);
|
||
}
|
||
};
|
||
|
||
g.addDialogEvent = function (event, callback) {
|
||
if (event !== 'close') {
|
||
window.addEventListener(event, callback, true);
|
||
}
|
||
g.dialog_events.push([event, callback]);
|
||
};
|
||
|
||
g.resetDialogEvents = function () {
|
||
var e;
|
||
|
||
while (e = g.dialog_events.pop()) {
|
||
if (e[0] === 'close') {
|
||
continue;
|
||
}
|
||
|
||
window.removeEventListener(e[0], e[1], true);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Open file preview
|
||
*/
|
||
g.openPreview = function (e) {
|
||
let type = e.getAttribute('data-mime');
|
||
let caption = e.getAttribute('data-caption');
|
||
|
||
if (!type) {
|
||
let url = e.href + (e.href.indexOf('?') > 0 ? '&' : '?') + '_dialog';
|
||
|
||
if (m = e.getAttribute('target').match(/_dialog=(.*)/)) {
|
||
url += '=' + m[1];
|
||
}
|
||
|
||
if (location.href.match(/_dialog/)) {
|
||
location.href = url;
|
||
return false;
|
||
}
|
||
|
||
g.openFrameDialog(url, {
|
||
'height': e.getAttribute('data-dialog-height') || 'auto',
|
||
'classname': e.getAttribute('data-dialog-class'),
|
||
'on_close': e.getAttribute('data-dialog-on-close') == 1,
|
||
caption
|
||
});
|
||
return false;
|
||
}
|
||
|
||
if (type.match(/^image\//)) {
|
||
var i = document.createElement('img');
|
||
i.src = e.href;
|
||
i.draggable = false;
|
||
i.onclick = () => g.navigateToPreview(e);
|
||
}
|
||
else if (type.match(/^audio\//)) {
|
||
var i = document.createElement('audio');
|
||
i.autoplay = true;
|
||
i.controls = true;
|
||
i.src = e.href;
|
||
i.draggable = false;
|
||
}
|
||
else if (type.match(/^video\/|^application\/ogg$/)) {
|
||
var i = document.createElement('video');
|
||
i.autoplay = true;
|
||
i.controls = true;
|
||
i.src = e.href;
|
||
i.draggable = false;
|
||
}
|
||
else {
|
||
let url = e.href + (e.href.indexOf('?') > 0 ? '&' : '?') + '_dialog';
|
||
g.openFrameDialog(url, {height: '90%', caption});
|
||
return false;
|
||
}
|
||
|
||
g.replaceDialog(i, {caption, 'click_to_close': true});
|
||
|
||
g.setDialogKey('ArrowLeft', () => g.navigateToPreview(e, true));
|
||
g.setDialogKey('ArrowRight', () => g.navigateToPreview(e));
|
||
|
||
var evt = e => { e.stopPropagation(); e.preventDefault(); g.closeDialog(); };
|
||
g.addDialogEvent('swipeup', evt);
|
||
i.addEventListener('swipeup', evt);
|
||
|
||
return false;
|
||
};
|
||
|
||
/**
|
||
* Navigate between elements to preview (eg. images)
|
||
*/
|
||
g.navigateToPreview = function (element, to_prev) {
|
||
var preview_items = document.querySelectorAll('a[target="_dialog"][data-mime]');
|
||
preview_items = Array.from(preview_items).filter((e) => e.dataset.mime.match(/^(audio|video|image)\//));
|
||
var next = null;
|
||
var prev = null;
|
||
|
||
for (var i = 0; i < preview_items.length; i++) {
|
||
var item = preview_items[i];
|
||
|
||
if (item.href === element.href) {
|
||
if (to_prev) {
|
||
next = prev;
|
||
break;
|
||
}
|
||
|
||
next = false;
|
||
continue;
|
||
}
|
||
|
||
if (next === false) {
|
||
next = item;
|
||
break;
|
||
}
|
||
|
||
prev = item;
|
||
}
|
||
|
||
if (!next) {
|
||
g.closeDialog();
|
||
return;
|
||
}
|
||
|
||
g.openPreview(next);
|
||
return false;
|
||
};
|
||
|
||
g.onload(() => {
|
||
document.querySelectorAll('input[data-input="date"]').forEach((e) => {
|
||
g.enhanceDateField(e);
|
||
});
|
||
|
||
document.querySelectorAll('input[data-input="hue"]').forEach((e) => {
|
||
g.enhanceHueField(e);
|
||
});
|
||
|
||
document.querySelectorAll('input[type="password"]:not([readonly]):not([disabled]):not(.hidden)').forEach((e) => {
|
||
g.enhancePasswordField(e);
|
||
});
|
||
|
||
// Enhance file inputs to add image preview, paste support, etc.
|
||
if (document.querySelector('input[type="file"][data-enhanced]')) {
|
||
g.script('scripts/inputs/file.js');
|
||
}
|
||
|
||
if (document.querySelector('input[list], textarea[list]')) {
|
||
g.script('scripts/inputs/datalist.js');
|
||
}
|
||
|
||
var dropdown;
|
||
|
||
var closeDropdownEvent = (evt) => {
|
||
if ((evt.type === 'keydown' && evt.key === 'Escape')
|
||
|| (evt.type === 'click' && !dropdown.contains(evt.target))) {
|
||
closeDropdown();
|
||
evt.preventDefault();
|
||
return false;
|
||
}
|
||
};
|
||
|
||
var closeDropdown = () => {
|
||
dropdown.classList.remove('open');
|
||
dropdown.setAttribute('aria-expanded', 'false');
|
||
dropdown = null;
|
||
window.removeEventListener('keydown', closeDropdownEvent, {'capture': true});
|
||
window.removeEventListener('click', closeDropdownEvent, {'capture': true});
|
||
};
|
||
|
||
var openDropdown = (e) => {
|
||
if (e.classList.contains('open')) {
|
||
return true;
|
||
}
|
||
|
||
dropdown = e;
|
||
e.classList.add('open');
|
||
e.setAttribute('aria-expanded', 'true');
|
||
window.addEventListener('keydown', closeDropdownEvent, {'capture': true});
|
||
window.addEventListener('click', closeDropdownEvent, {'capture': true});
|
||
return false;
|
||
}
|
||
|
||
document.querySelectorAll('nav.dropdown').forEach(e => {
|
||
e.onclick = () => openDropdown(e);
|
||
});
|
||
});
|
||
|
||
g.onload(() => {
|
||
let forms = document.forms;
|
||
|
||
if (forms.length != 1) return;
|
||
|
||
// Disable progress on search or the form will stay blurred when clicking export buttons
|
||
if (forms[0].hasAttribute('data-disable-progress')) return;
|
||
|
||
forms[0].addEventListener('submit', (e) => {
|
||
if (e.defaultPrevented) return;
|
||
forms[0].classList.add('progressing');
|
||
});
|
||
});
|
||
|
||
// To be able to select a whole table line just by clicking the row
|
||
g.onload(function () {
|
||
var tableActions = document.querySelectorAll('form table tfoot .actions select');
|
||
|
||
for (var i = 0; i < tableActions.length; i++)
|
||
{
|
||
tableActions[i].onchange = function () {
|
||
if (!this.value) {
|
||
return;
|
||
}
|
||
|
||
if (!this.form.querySelector('table tbody input[type=checkbox]:checked'))
|
||
{
|
||
this.selectedIndex = 0;
|
||
return !window.alert("Aucune ligne sélectionnée !");
|
||
}
|
||
|
||
var action = this.form.getAttribute('action');
|
||
var target = this.form.getAttribute('target');
|
||
|
||
if (this.hasAttribute('data-form-action')) {
|
||
this.form.action = this.dataset.formAction;
|
||
}
|
||
|
||
if (this.getAttribute('data-form-target') === '_dialog') {
|
||
this.form.target = '_dialog'
|
||
}
|
||
|
||
if (this.options[this.selectedIndex].hasAttribute('data-no-dialog')) {
|
||
this.form.target = '';
|
||
}
|
||
|
||
if (this.form.target === '_dialog') {
|
||
g.openFormInDialog(this.form);
|
||
}
|
||
else {
|
||
// Not sure if this is required?
|
||
this.form.dispatchEvent(new Event('submit'));
|
||
this.form.submit();
|
||
}
|
||
|
||
this.form.action = action;
|
||
this.form.target = target || '';
|
||
this.selectedIndex = 0;
|
||
};
|
||
}
|
||
|
||
// Ajouter action check/uncheck sur les checkbox de raccourci dans les tableaux
|
||
document.querySelectorAll('table thead input[type=checkbox], table tfoot input[type=checkbox]').forEach((elm) => {
|
||
elm.addEventListener('change', g.checkUncheck);
|
||
});
|
||
|
||
document.querySelectorAll('table tbody td.check input[type=checkbox]').forEach((elm) => {
|
||
elm.addEventListener('change', () => {
|
||
elm.parentNode.parentNode.classList.toggle('checked', elm.checked);
|
||
});
|
||
});
|
||
});
|
||
|
||
g.onload(() => g.resizeParentDialog(), false);
|
||
|
||
g.onload(() => {
|
||
// File drag and drop support
|
||
if ($('[data-upload-url]').length) {
|
||
g.script('scripts/file_drag.js');
|
||
}
|
||
});
|
||
|
||
// Add support for accesskeys, even if web browser doesn't support it, or has a weird keys combination
|
||
window.addEventListener('keydown', (e) => {
|
||
if (!e.altKey || !e.shiftKey) {
|
||
return true;
|
||
}
|
||
|
||
if (e.key.length === 1 && (el = document.querySelector('[accesskey="' + e.key + '"]'))) {
|
||
el.click();
|
||
return true;
|
||
}
|
||
|
||
// Highlight accesskeys
|
||
document.body.classList.add('accesskeys');
|
||
});
|
||
window.addEventListener('keyup', () => { document.body.classList.remove('accesskeys'); });
|
||
|
||
// Implement swipeleft/swiperight events
|
||
let touch_start_x = 0;
|
||
let touch_start_y = 0;
|
||
let touch_start_el = null;
|
||
|
||
window.addEventListener('touchstart', e => {
|
||
touch_start_el = e.target;
|
||
touch_start_x = e.changedTouches[0].screenX;
|
||
touch_start_y = e.changedTouches[0].screenY;
|
||
});
|
||
|
||
window.addEventListener('touchend', e => {
|
||
if (touch_start_el !== e.target) {
|
||
return;
|
||
}
|
||
|
||
var touch_end_x = e.changedTouches[0].screenX;
|
||
var touch_end_y = e.changedTouches[0].screenY;
|
||
var distance_x = touch_end_x - touch_start_x;
|
||
var distance_y = touch_end_y - touch_start_y;
|
||
var direction = null;
|
||
|
||
if (Math.abs(distance_x) > Math.abs(distance_y)) {
|
||
if (distance_x < -20) {
|
||
direction = 'left';
|
||
}
|
||
else if (distance_x > 20) {
|
||
direction = 'right';
|
||
}
|
||
}
|
||
else {
|
||
if (distance_y < -20) {
|
||
direction = 'up';
|
||
}
|
||
else if (distance_y > 20) {
|
||
direction = 'down';
|
||
}
|
||
}
|
||
|
||
if (!direction) {
|
||
return;
|
||
}
|
||
|
||
touch_start_el.dispatchEvent(new CustomEvent('swipe' + direction, {
|
||
bubbles: true,
|
||
cancelable: true,
|
||
detail: {direction, distance_x, distance_y}
|
||
}));
|
||
});
|
||
})(); |