Your IP : 216.73.216.59


Current Path : /home/quelfutuu/www/plugins/auto/saisies/v6.3.1/javascript/
Upload File :
Current File : /home/quelfutuu/www/plugins/auto/saisies/v6.3.1/javascript/saisies.js

jQuery(function(){
	saisies_fieldset_pliable();
	saisies_fieldset_onglet();
	saisies_multi_novalidate();
	SaisiesDateJourMoisAnnee.init();
	onAjaxLoad(saisies_fieldset_pliable);
	onAjaxLoad(saisies_fieldset_onglet);
	onAjaxLoad(saisies_multi_novalidate);
	onAjaxLoad(saisies_date_jour_mois_annee);
});

/**
 * Rend certains fieldsets pliables
 *
 * Il s'agit des fieldsets portant les classes "fieldset.pliable"
 * Non cumulable avec les fieldsets en onglets.
 */
function saisies_fieldset_pliable(){
	// On cherche les groupes de champs pliables
	jQuery('.fieldset.pliable')
		.each(function(){
			var fieldset = jQuery(this);
			var groupe = jQuery(this).find('> .editer-groupe');
			var legend = jQuery(this).find('> legend');

			// On éviter de plier un fieldset qui contient des erreurs lors de
			// l'initialisation.
			if (fieldset.find('.erreur').length > 0) {
				fieldset.removeClass('plie');
			}

			// S'il est déjà plié on cache le contenu
			if (fieldset.is('.plie'))
				groupe.hide();

			// Ensuite on ajoute une action sur le titre
			legend
				.unbind('click')
				.click(
					function(){
						fieldset.toggleClass('plie');
						if (groupe.is(':hidden'))
							groupe.show();
						else
							groupe.hide();
					}
				);
		});
};

/**
 * Tranforme certains fieldsets en onglets
 *
 * - Ceux portant les classes "fieldset.fieldset_onglet".
 * - Accessible à l'exception de la navigation au clavier.
 * - Les onglets sont persistants si les fieldsets possèdent un id ou un data-id.
 * - Non cumulable avec les fieldsets pliables.
 *
 * Markup inspiré de https://van11y.net/fr/onglets-accessibles/
 */
function saisies_fieldset_onglet() {

	// Classes utilisées
	var classes = {
		// conteneur général
		wrapper_horizontal: 'saisies-onglets',
		wrapper_vertical: 'saisies-onglets saisies-onglets-verticaux',
		// Menu
		tablist:       'saisies-menu-onglets',
		tablist_items: 'saisies-menu-onglets__items',
		tablist_item:  'saisies-menu-onglets__item',
		tablist_link:  'saisies-menu-onglets__lien',
		active:        'actif',
		error:         'erreur',
		scrollable:    'scrollable',
		// contenus (les fieldsets)
		tabscontents:  'saisies-contenus-onglets',
		tabcontent:    'saisies-contenu-onglet afficher_si_sans_visuel', // en complément de .fieldset_onglet
	}
	var selecteur_fieldset = '.fieldset.fieldset_onglet:not(.pliable)';
	var storage = window.sessionStorage;
	// Générer les onglets
	var init = function() {
		$.each(collections_fieldsets(), function(i, $fieldsets) {
			// On peut avoir des onglets horizontaux dans les onglets verticaux, donc il faut reinitialiser à chaque niveau
			classes.wrapper = classes.wrapper_horizontal;
			// A-t-on ne serait-ce qu'un onglet vertical ? Si oui, on considère que tout est en vertical
			$fieldsets.each(function() {
				if ($(this).hasClass('fieldset_onglet_vertical')) {
					classes.wrapper = classes.wrapper_vertical;
					return false;
				}
			});

			var
				$conteneur				= $('<div class="'+classes.wrapper+'"></div>'),
				$menu							= $('<nav class="'+classes.tablist+'"><ul class="'+classes.tablist_items+'" role="tablist"></ul></nav>'),
				$contenus					= $('<div class="'+classes.tabscontents+'"></div>'),
				ids_contenus			= [],
				$first_fieldset		= $fieldsets.first(),
				id_menu						= null;

			// On insère un conteneur général avant le premier fieldset de la série
			// puis celui des contenus à l'intérieur.
			$conteneur.append($contenus).insertBefore($first_fieldset);

			// On parcourt la série de fieldsets pour préparer
			// les entrées du menu, les interactions et les contenus
			$fieldsets.each(function() {

				var
					$contenu      = $(this),
					id_persistant = $contenu.attr('id') || $contenu.attr('data-id'),
					afficher_si		= $contenu.attr('data-afficher_si') || 'true',
					id_contenu    = id_persistant || randomId(),
					id_onglet     = 'onglet-' + id_contenu;

				// On ajoute les attributs nécessaire : id, classe, role et aria
				// puis on le cache d'office et on le déplace dans le conteneur.

				$contenu
					.attr('id', id_contenu)
					.addClass(classes.tabcontent)
					.attr('role', 'tabpanel')
					.attr('aria-labelledby', id_onglet)
					.attr('data-saisies-onglet', true) // pour s'assurer de ne pas passer plusieurs fois
					.hide().attr('hidden', '')
					.appendTo($contenus);

				// On récupère le titre et on le cache
				var titre = $contenu.find('legend').first().hide().text();

				// On crée l'onglet avec son interaction
				var $onglet = $('<li class="'+classes.tablist_item+'"><a class="'+classes.tablist_link+'" href="#'+id_contenu+'" id="'+id_onglet+'" aria-controls="'+id_contenu+'" role="tab" aria-selected="false" tabindex="-1">'+titre+'</a></li>');
				var autoriser_changement = false;
				$onglet
					.attr('data-afficher_si', afficher_si)
					.click(function() {
						var sibling_active = $(this).siblings().has('.' + classes.active);
						sibling_active.each(function() {
							var onglet_reference = $(this).find('a').attr('href');
							autoriser_changement = container_reportValidity(onglet_reference);
						});
						if (!sibling_active.length) {
							autoriser_changement = true;
						}
						if (autoriser_changement) {
							activer_onglet($(this).find('.'+classes.tablist_link));
						}
						return false;
					});

				// Lorsqu'on masque l'onglet avec afficher_si, désactiver l'onglet, puis se rendre si possible au premier onglet
				$onglet.on('afficher_si_masque_pre', function() {
					$this_onglet = $(this);
					$lien = $this_onglet.children('a');
					// Si c'est onglet actif, on ferme l'onglet et on cherche le premier onglet dispo
					if ($lien.attr('aria-selected') == 'true') {
						$nouvel_onglet = $this_onglet.siblings().not('.afficher_si_masque').first().children('a')
						desactiver_onglet($lien);//On désactive dans tous les cas l'onglet courant, car cela se trouve il n'y aura pas d'autres onglets à activer
						activer_onglet($nouvel_onglet);
					}
				});


				// Lorsqu'on rend visible l'onglet après un afficher_si, si c'est le seul, y aller directement
				$onglet.on('afficher_si_visible_post', function() {
					$voisins = $(this).siblings().not('.afficher_si_masque');
					if (!$voisins.length) {
						activer_onglet($onglet.children('a'));
					}
				});


				// On note l'id persistant
				if (id_persistant) {
					ids_contenus.push(id_persistant);
				}

				// S'il y a des erreurs dans cette partie du contenu, on met une classe "erreur" à l'onglet aussi
				if ($contenu.find('.editer.erreur').length) {
					$onglet.children('a').addClass(classes.error);
				}
				// On ajoute l'onglet au menu
				$menu.find('.'+classes.tablist_items).append($onglet);
			});

			// On insère le menu dans le DOM.
			// Si *tous* les fieldsets on un id persistant, on peut s'en servir pour celui du menu,
			// ce qui permet la navigation persistante.
			// l'id du menu sera utilisé comme clé dans la session, on le simplifie avec un hash.
			if (ids_contenus.length === $fieldsets.length) {
				id_menu = 'onglets-'+hashCode(ids_contenus.join(''));
				$menu.attr('data-id', id_menu);
			}
			$menu.prependTo($conteneur);

			// Indiquer si le menu doit être scrollable
			if ($menu[0].scrollWidth > $menu[0].clientWidth) {
				$menu.addClass(classes.scrollable);
			}

			// On active l'onglet par défaut, par ordre de priorité :
			//  - le premier avec une erreur au sein de son groupe d'onglets
			//	- celui en session s'il existe
			//	- le 1er trouvé
			var $onglet_defaut;
			if ($('.' + classes.tablist_link + '.' + classes.error).length > 0) {
				$onglet_defaut = $menu.find('.' + classes.tablist_link + '.' + classes.error).first();
			} else if (storage.getItem(id_menu) !== null && $('#'+escapeId(storage.getItem(id_menu))).length > 0) {
				$onglet_defaut = $('#'+escapeId(storage.getItem(id_menu)));
			} else {
				$onglet_defaut = $menu.find('.'+classes.tablist_link).first();
			}
			activer_onglet($onglet_defaut, 0, false);

		});

		// Si un problème de validation sur un champ, basculer vers le fieldset qui contient l'élèment invalide
		// Note : on initialise cela en dehors de l'initialisation des onglets
		// Car un même champ peut être dans plusieurs onglets (onglets imbriqués)
		// Et on ne veut pas de double écouteur
		$('.' + classes.tabscontents + ' [name]').on('invalid', function() {
			// On fait cela lot d'onglets par lot d'onglets
			$(this).parents('form').find('.' + classes.tabscontents).each(function() {
				var id_fieldset = '#' + $(this).find('> fieldset:invalid').first().attr('id');
				var $onglet = $('a[href="' + id_fieldset + '"]');
				activer_onglet($onglet);
			});
		});
	}

	// Retourne un tableau de collections de fieldsets
	// par log de fieldset coté à cote
	var collections_fieldsets = function() {
		var collections = [];
		$(selecteur_fieldset).each( function() {
			$fieldsets_niveau = $(this).add($(this).nextUntil(':not('+selecteur_fieldset+')')),
			parsed = $(this).data('saisies-onglet-parsed') || false;
			if (!parsed) {
				collections.push($fieldsets_niveau);
				$fieldsets_niveau.each( function() {
					$(this).data('saisies-onglet-parsed', true);
				});
			}
		});
		return collections;
	}

	// Activer un onglet
	// en commencant par désactiver son voisin
	// @param object $onglet Élément <a>
	var activer_onglet = function( $onglet, duree = 150, persistant = true ) {
		$onglet_actuel = $onglet.parent().siblings().has('.' + classes.active).find('.' + classes.tablist_link);
		desactiver_onglet($onglet_actuel);
		if ($onglet.length) {
			var $contenu = $(escapeId($onglet.attr('href')));
			$onglet.addClass(classes.active).attr('aria-selected', true).removeAttr('tabindex');
			$contenu.fadeIn(duree).removeAttr('hidden');
			// Mettre en session si on a ce qu'il faut
			var id_menu = $onglet.parents('.'+classes.tablist).attr('data-id') || null;
			if (persistant && id_menu) {
				storage.setItem(id_menu, $onglet.attr('id'));
			}
		}
	}

	// Désactiver un onglet
	// @param object $onglet Élément <a>
	var desactiver_onglet = function( $onglet, duree = 150 ) {
		if ($onglet.length) {
			var $contenu = $(escapeId($onglet.attr('href')));
			$onglet.removeClass(classes.active).attr('aria-selected', false).attr('tabindex', -1);
			$contenu.hide().attr('hidden', '');
		}
	}

	// Échapper les ids pour ne pas faire couiner jQuery
	var escapeId = function ( id ) {
		id = (id || '').replace(/[^\d\w_\-\#]/gi, '\\$&');
		return id;
	}

	// Retourne un identifiant aléatoire
	// https://stackoverflow.com/a/8084248
	var randomId = function (taille = 8) {
		var random = (Math.random() + 1).toString(36);
		return random.substring(random.length - taille);
	}

	// Hash simple et rapide
	// https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0
	var hashCode = function(s) {
		for (var i = 0, h = 0; i < s.length; i++)
			h = Math.imul(31, h) + s.charCodeAt(i) | 0;
		return Math.abs(h);
	}

	/*
	 * Vérifier la validité de l'ensemble des champs contenu dans element
	 * @param string selector : un critère de selection
	 * @return bool
	**/
	var container_reportValidity = function(selector) {
		var retour = true;
		$(selector).find('[name]').each(function() {
			retour = retour && $(this).get(0).reportValidity();//Un seul `false` et tout est `false`
		});
		return retour;
	}
	// C'est parti
	init();
}

function saisies_date_jour_mois_annee_changer_date(me, datetime) {
	var champ = jQuery(me);
	var li = champ.closest('.editer');
	var	jour = jQuery.trim(li.find('.date_jour').val());
	var	mois = jQuery.trim(li.find('.date_mois').val());
	var	annee = jQuery.trim(li.find('.date_annee').val());
	var	date = jQuery.trim(li.find('.datetime').val());

	while(jour.length < 2) {jour = '0' + jour;}
	while(mois.length < 2) {mois = '0' + mois;}
	while(annee.length < 4) {annee = '0' + annee;}

	if (datetime == 'oui') {
		heure = date.substring(10);
		if (!heure || !(heure.length == 9)) {
			heure = ' 00:00:00';
		}
		date = annee + '-' + mois + '-' + jour + heure;
	}
	else {
		date = annee + '-' + mois + '-' + jour;
	}
	li.find('.datetime').attr('value', date);
}

/**
 * Gestion des saisies date_jour_mois_annee
 */
class SaisiesDateJourMoisAnnee {
	constructor(container) {
		this.container = container;
		this.dateWrapper = container.querySelector('[data-composant="date"]');
		this.dateInput = container.querySelector('[data-composant-input="date"]');

		this.jourInput = container.querySelector('[data-composant-input="jour"]');
		this.moisInput = container.querySelector('[data-composant-input="mois"]');
		this.anneeInput = container.querySelector('[data-composant-input="annee"]');

		this.jourWrapper = container.querySelector('[data-composant="jour"]');
		this.moisWrapper = container.querySelector('[data-composant="mois"]');
		this.anneeWrapper = container.querySelector('[data-composant="annee"]');

		this.resetBtn = container.querySelector('.btn_reset');
		this.format = this.dateInput.dataset.format || 'datetime';
	}

	// Initialiser toutes les saisies
	static init() {
		document.querySelectorAll('.saisie_date_jour_mois_annee .composants-date.mode-jour-mois-annee').forEach(container => {
			if (container.hasAttribute('data-saisies-initialized')) return;

			const instance = new SaisiesDateJourMoisAnnee(container);
			instance.setup();
			instance.sync();
		});
	}

	// Setup d'une saisie : cacher l'input de date et afficher les autres
	setup() {
		this.container.setAttribute('data-saisies-initialized', 'true');

		// Cacher l'input de date
		this.dateInput.type = 'hidden';
		this.dateWrapper.classList.add('saisies-visually-hidden');

		// Afficher les autres inputs et le bouton reset
		[this.jourInput, this.moisInput, this.anneeInput].forEach((input) => {
			const wrapper = input.closest('[data-composant]');
			wrapper.removeAttribute('hidden');
			wrapper.style.removeProperty('display');
			if (input.dataset.disabled !== 'true') { // certains sont volontairement en disabled
				input.removeAttribute('disabled');
			}
		});
		this.resetBtn.style.removeProperty('display');
		this.resetBtn.removeAttribute('hidden');
	}

	// Synchronisation d'une saisie : synchroniser la date avec les autres inputs
	// et gestion du bouton reset
	sync() {
		const updateDateValue = () => {
			const jour = this.jourInput.hasAttribute('disabled') ? '' : this.jourInput.value.trim();
			const mois = this.moisInput.hasAttribute('disabled') ? '' : this.moisInput.value.trim();
			const annee = this.anneeInput.hasAttribute('disabled') ? '' : this.anneeInput.value.trim();

			const allEmpty = !jour && !mois && !annee;
			// const someEmpty = !jour || !mois || !annee;

			if (allEmpty) {
				this.dateInput.value = '';
				return;
			}

			// Normaliser
			// Si des éléments sont en disabled,
			// en fallback on s'aligne sur les valeurs minimales tolérées par PHP.
			const j = jour ? jour.padStart(2, '0') : '01';
			const m = mois ? mois.padStart(2, '0') : '01';
			const a = annee ? annee.padStart(4, '0') : '0000';

			let value = `${a}-${m}-${j}`;
			if (this.format === 'datetime') {
				value += ' 00:00:00';
			}

			this.dateInput.value = value;
			this.dateInput.dispatchEvent(new Event('change', { bubbles: true }));
		};

		[this.jourInput, this.moisInput, this.anneeInput].forEach(input => {
			input.addEventListener('input', updateDateValue);
		});

		// Clic sur le bouton reset
		if (this.resetBtn) {
			this.resetBtn.addEventListener('click', (e) => {
				e.preventDefault();
				[this.jourInput, this.moisInput, this.anneeInput].forEach(input => {
					if (
						!input.hasAttribute('readonly')
						&& !input.hasAttribute('disabled')
						&& input.dataset.readonly !== 'true'
						&& input.dataset.disabled !== 'true'
				) {
						input.value = '';
					}
				});
				updateDateValue();
			});
		}

		// Synchro initiale s'il y a des valeurs par défaut
		updateDateValue();
	}
}
// Fonction wrapper pour relancer lors de rechargements ajax
function saisies_date_jour_mois_annee() {
	SaisiesDateJourMoisAnnee.init();
}

/** Ne pas valider lors des retours arrières sur multiétape **/
function saisies_multi_novalidate() {
	$('[name^="_retour_etape"],[name="aller_a_etape"]').click(function() {
		$(this).parents('form').attr('novalidate', 'novalidate');
	});
}