PageSourceSearch

https://idees-beaumont.org/plugins/auto/saisies/v6.3.8/javascript/saisies.js?1790147101

js idees-beaumont.org collected 2026-10-02 19:57:22 UTC 16,474 bytes, 468 lines download raw bytes

1jQuery(function(){
2	saisies_fieldset_pliable();
3	saisies_fieldset_onglet();
4	saisies_multi_novalidate();
5	SaisiesDateJourMoisAnnee.init();
6	onAjaxLoad(saisies_fieldset_pliable);
7	onAjaxLoad(saisies_fieldset_onglet);
8	onAjaxLoad(saisies_multi_novalidate);
9	onAjaxLoad(saisies_date_jour_mois_annee);
10});
11
12/**
13 * Rend certains fieldsets pliables
14 *
15 * Il s'agit des fieldsets portant les classes "fieldset.pliable"
16 * Non cumulable avec les fieldsets en onglets.
17 */
18function saisies_fieldset_pliable(){
19	// On cherche les groupes de champs pliables
20	jQuery('.fieldset.pliable')
21		.each(function(){
22			var fieldset = jQuery(this);
23			var groupe = jQuery(this).find('> .editer-groupe');
24			var legend = jQuery(this).find('> legend');
25
26			// On éviter de plier un fieldset qui contient des erreurs lors de
27			// l'initialisation.
28			if (fieldset.find('.erreur').length > 0) {
29				fieldset.removeClass('plie');
30			}
31
32			// S'il est déjà plié on cache le contenu
33			if (fieldset.is('.plie'))
34				groupe.hide();
35
36			// Ensuite on ajoute une action sur le titre
37			legend
38				.unbind('click')
39				.click(
40					function(){
41						fieldset.toggleClass('plie');
42						if (groupe.is(':hidden'))
43							groupe.show();
44						else
45							groupe.hide();
46					}
47				);
48		});
49};
50
51/**
52 * Tranforme certains fieldsets en onglets
53 *
54 * - Ceux portant les classes "fieldset.fieldset_onglet".
55 * - Accessible à l'exception de la navigation au clavier.
56 * - Les onglets sont persistants si les fieldsets possèdent un id ou un data-id.
57 * - Non cumulable avec les fieldsets pliables.
58 *
59 * Markup inspiré de https://van11y.net/fr/onglets-accessibles/
60 */
61function saisies_fieldset_onglet() {
62
63	// Classes utilisées
64	var classes = {
65		// conteneur général
66		wrapper_horizontal: 'saisies-onglets',
67		wrapper_vertical: 'saisies-onglets saisies-onglets-verticaux',
68		// Menu
69		tablist:       'saisies-menu-onglets',
70		tablist_items: 'saisies-menu-onglets__items',
71		tablist_item:  'saisies-menu-onglets__item',
72		tablist_link:  'saisies-menu-onglets__lien',
73		active:        'actif',
74		error:         'erreur',
75		scrollable:    'scrollable',
76		// contenus (les fieldsets)
77		tabscontents:  'saisies-contenus-onglets',
78		tabcontent:    'saisies-contenu-onglet afficher_si_sans_visuel', // en complément de .fieldset_onglet
79	}
80	var selecteur_fieldset = '.fieldset.fieldset_onglet:not(.pliable)';
81	var storage = window.sessionStorage;
82	// Générer les onglets
83	var init = function() {
84		$.each(collections_fieldsets(), function(i, $fieldsets) {
85			// On peut avoir des onglets horizontaux dans les onglets verticaux, donc il faut reinitialiser à chaque niveau
86			classes.wrapper = classes.wrapper_horizontal;
87			// A-t-on ne serait-ce qu'un onglet vertical ? Si oui, on considère que tout est en vertical
88			$fieldsets.each(function() {
89				if ($(this).hasClass('fieldset_onglet_vertical')) {
90					classes.wrapper = classes.wrapper_vertical;
91					return false;
92				}
93			});
94
95			var
96				$conteneur				= $('<div class="'+classes.wrapper+'"></div>'),
97				$menu							= $('<nav class="'+classes.tablist+'"><ul class="'+classes.tablist_items+'" role="tablist"></ul></nav>'),
98				$contenus					= $('<div class="'+classes.tabscontents+'"></div>'),
99				ids_contenus			= [],
100				$first_fieldset		= $fieldsets.first(),
101				id_menu						= null;
102
103			// On insère un conteneur général avant le premier fieldset de la série
104			// puis celui des contenus à l'intérieur.
105			$conteneur.append($contenus).insertBefore($first_fieldset);
106
107			// On parcourt la série de fieldsets pour préparer
108			// les entrées du menu, les interactions et les contenus
109			$fieldsets.each(function() {
110
111				var
112					$contenu      = $(this),
113					id_persistant = $contenu.attr('id') || $contenu.attr('data-id'),
114					afficher_si		= $contenu.attr('data-afficher_si') || 'true',
115					id_contenu    = id_persistant || randomId(),
116					id_onglet     = 'onglet-' + id_contenu;
117
118				// On ajoute les attributs nécessaire : id, classe, role et aria
119				// puis on le cache d'office et on le déplace dans le conteneur.
120
121				$contenu
122					.attr('id', id_contenu)
123					.addClass(classes.tabcontent)
124					.attr('role', 'tabpanel')
125					.attr('aria-labelledby', id_onglet)
126					.attr('data-saisies-onglet', true) // pour s'assurer de ne pas passer plusieurs fois
127					.hide().attr('hidden', '')
128					.appendTo($contenus);
129
130				// On récupère le titre et on le cache
131				var titre = $contenu.find('legend').first().hide().text();
132
133				// On crée l'onglet avec son interaction
134				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>');
135				var autoriser_changement = false;
136				$onglet
137					.attr('data-afficher_si', afficher_si)
138					.click(function() {
139						var sibling_active = $(this).siblings().has('.' + classes.active);
140						sibling_active.each(function() {
141							var onglet_reference = $(this).find('a').attr('href');
142							autoriser_changement = container_reportValidity(onglet_reference);
143						});
144						if (!sibling_active.length) {
145							autoriser_changement = true;
146						}
147						if (autoriser_changement) {
148							activer_onglet($(this).find('.'+classes.tablist_link));
149						}
150						return false;
151					});
152
153				// Lorsqu'on masque l'onglet avec afficher_si, désactiver l'onglet, puis se rendre si possible au premier onglet
154				$onglet.on('afficher_si_masque_pre', function() {
155					$this_onglet = $(this);
156					$lien = $this_onglet.children('a');
157					// Si c'est onglet actif, on ferme l'onglet et on cherche le premier onglet dispo
158					if ($lien.attr('aria-selected') == 'true') {
159						$nouvel_onglet = $this_onglet.siblings().not('.afficher_si_masque').first().children('a')
160						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
161						activer_onglet($nouvel_onglet);
162					}
163				});
164
165
166				// Lorsqu'on rend visible l'onglet après un afficher_si, si c'est le seul, y aller directement
167				$onglet.on('afficher_si_visible_post', function() {
168					$voisins = $(this).siblings().not('.afficher_si_masque');
169					if (!$voisins.length) {
170						activer_onglet($onglet.children('a'));
171					}
172				});
173
174
175				// On note l'id persistant
176				if (id_persistant) {
177					ids_contenus.push(id_persistant);
178				}
179
180				// S'il y a des erreurs dans cette partie du contenu, on met une classe "erreur" à l'onglet aussi
181				if ($contenu.find('.editer.erreur').length) {
182					$onglet.children('a').addClass(classes.error);
183				}
184				// On ajoute l'onglet au menu
185				$menu.find('.'+classes.tablist_items).append($onglet);
186			});
187
188			// On insère le menu dans le DOM.
189			// Si *tous* les fieldsets on un id persistant, on peut s'en servir pour celui du menu,
190			// ce qui permet la navigation persistante.
191			// l'id du menu sera utilisé comme clé dans la session, on le simplifie avec un hash.
192			if (ids_contenus.length === $fieldsets.length) {
193				id_menu = 'onglets-'+hashCode(ids_contenus.join(''));
194				$menu.attr('data-id', id_menu);
195			}
196			$menu.prependTo($conteneur);
197
198			// Indiquer si le menu doit être scrollable
199			if ($menu[0].scrollWidth > $menu[0].clientWidth) {
200				$menu.addClass(classes.scrollable);
201			}
202
203			// On active l'onglet par défaut, par ordre de priorité :
204			//  - le premier avec une erreur au sein de son groupe d'onglets
205			//	- celui en session s'il existe
206			//	- le 1er trouvé
207			var $onglet_defaut;
208			if ($('.' + classes.tablist_link + '.' + classes.error).length > 0) {
209				$onglet_defaut = $menu.find('.' + classes.tablist_link + '.' + classes.error).first();
210			} else if (storage.getItem(id_menu) !== null && $('#'+escapeId(storage.getItem(id_menu))).length > 0) {
211				$onglet_defaut = $('#'+escapeId(storage.getItem(id_menu)));
212			} else {
213				$onglet_defaut = $menu.find('.'+classes.tablist_link).first();
214			}
215			activer_onglet($onglet_defaut, 0, false);
216
217		});
218
219		// Si un problème de validation sur un champ, basculer vers le fieldset qui contient l'élèment invalide
220		// Note : on initialise cela en dehors de l'initialisation des onglets
221		// Car un même champ peut être dans plusieurs onglets (onglets imbriqués)
222		// Et on ne veut pas de double écouteur
223		$('.' + classes.tabscontents + ' [name]').on('invalid', function() {
224			// On fait cela lot d'onglets par lot d'onglets
225			$(this).parents('form').find('.' + classes.tabscontents).each(function() {
226				var id_fieldset = '#' + $(this).find('> fieldset:invalid').first().attr('id');
227				var $onglet = $('a[href="' + id_fieldset + '"]');
228				activer_onglet($onglet);
229			});
230		});
231	}
232
233	// Retourne un tableau de collections de fieldsets
234	// par log de fieldset coté à cote
235	var collections_fieldsets = function() {
236		var collections = [];
237		$(selecteur_fieldset).each( function() {
238			$fieldsets_niveau = $(this).add($(this).nextUntil(':not('+selecteur_fieldset+')')),
239			parsed = $(this).data('saisies-onglet-parsed') || false;
240			if (!parsed) {
241				collections.push($fieldsets_niveau);
242				$fieldsets_niveau.each( function() {
243					$(this).data('saisies-onglet-parsed', true);
244				});
245			}
246		});
247		return collections;
248	}
249
250	// Activer un onglet
251	// en commencant par désactiver son voisin
252	// @param object $onglet Élément <a>
253	var activer_onglet = function( $onglet, duree = 150, persistant = true ) {
254		$onglet_actuel = $onglet.parent().siblings().has('.' + classes.active).find('.' + classes.tablist_link);
255		desactiver_onglet($onglet_actuel);
256		if ($onglet.length) {
257			var $contenu = $(escapeId($onglet.attr('href')));
258			$onglet.addClass(classes.active).attr('aria-selected', true).removeAttr('tabindex');
259			$contenu.fadeIn(duree).removeAttr('hidden');
260			// Mettre en session si on a ce qu'il faut
261			var id_menu = $onglet.parents('.'+classes.tablist).attr('data-id') || null;
262			if (persistant && id_menu) {
263				storage.setItem(id_menu, $onglet.attr('id'));
264			}
265		}
266	}
267
268	// Désactiver un onglet
269	// @param object $onglet Élément <a>
270	var desactiver_onglet = function( $onglet, duree = 150 ) {
271		if ($onglet.length) {
272			var $contenu = $(escapeId($onglet.attr('href')));
273			$onglet.removeClass(classes.active).attr('aria-selected', false).attr('tabindex', -1);
274			$contenu.hide().attr('hidden', '');
275		}
276	}
277
278	// Échapper les ids pour ne pas faire couiner jQuery
279	var escapeId = function ( id ) {
280		id = (id || '').replace(/[^\d\w_\-\#]/gi, '\\$&');
281		return id;
282	}
283
284	// Retourne un identifiant aléatoire
285	// https://stackoverflow.com/a/8084248
286	var randomId = function (taille = 8) {
287		var random = (Math.random() + 1).toString(36);
288		return random.substring(random.length - taille);
289	}
290
291	// Hash simple et rapide
292	// https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0
293	var hashCode = function(s) {
294		for (var i = 0, h = 0; i < s.length; i++)
295			h = Math.imul(31, h) + s.charCodeAt(i) | 0;
296		return Math.abs(h);
297	}
298
299	/*
300	 * Vérifier la validité de l'ensemble des champs contenu dans element
301	 * @param string selector : un critère de selection
302	 * @return bool
303	**/
304	var container_reportValidity = function(selector) {
305		var retour = true;
306		$(selector).find('[name]').each(function() {
307			let champ = $(this).get(0);
308
309			// Si c'est bien un champ de formulaire, pouvant être vérifié en JS
310			if (typeof champ.reportValidity === 'function') {
311				retour = retour && champ.reportValidity(); //Un seul `false` et tout est `false`
312			}
313		});
314
315		return retour;
316	}
317	
318	// C'est parti
319	init();
320}
321
322function saisies_date_jour_mois_annee_changer_date(me, datetime) {
323	var champ = jQuery(me);
324	var li = champ.closest('.editer');
325	var	jour = jQuery.trim(li.find('.date_jour').val());
326	var	mois = jQuery.trim(li.find('.date_mois').val());
327	var	annee = jQuery.trim(li.find('.date_annee').val());
328	var	date = jQuery.trim(li.find('.datetime').val());
329
330	while(jour.length < 2) {jour = '0' + jour;}
331	while(mois.length < 2) {mois = '0' + mois;}
332	while(annee.length < 4) {annee = '0' + annee;}
333
334	if (datetime == 'oui') {
335		heure = date.substring(10);
336		if (!heure || !(heure.length == 9)) {
337			heure = ' 00:00:00';
338		}
339		date = annee + '-' + mois + '-' + jour + heure;
340	}
341	else {
342		date = annee + '-' + mois + '-' + jour;
343	}
344	li.find('.datetime').attr('value', date);
345}
346
347/**
348 * Gestion des saisies date_jour_mois_annee
349 */
350class SaisiesDateJourMoisAnnee {
351	constructor(container) {
352		this.container = container;
353		this.dateWrapper = container.querySelector('[data-composant="date"]');
354		this.dateInput = container.querySelector('[data-composant-input="date"]');
355
356		this.jourInput = container.querySelector('[data-composant-input="jour"]');
357		this.moisInput = container.querySelector('[data-composant-input="mois"]');
358		this.anneeInput = container.querySelector('[data-composant-input="annee"]');
359
360		this.jourWrapper = container.querySelector('[data-composant="jour"]');
361		this.moisWrapper = container.querySelector('[data-composant="mois"]');
362		this.anneeWrapper = container.querySelector('[data-composant="annee"]');
363
364		this.resetBtn = container.querySelector('.btn_reset');
365		this.format = this.dateInput.dataset.format || 'datetime';
366	}
367
368	// Initialiser toutes les saisies
369	static init() {
370		document.querySelectorAll('.saisie_date_jour_mois_annee .composants-date.mode-jour-mois-annee').forEach(container => {
371			if (container.hasAttribute('data-saisies-initialized')) return;
372
373			const instance = new SaisiesDateJourMoisAnnee(container);
374			instance.setup();
375			instance.sync();
376		});
377	}
378
379	// Setup d'une saisie : cacher l'input de date et afficher les autres
380	setup() {
381		this.container.setAttribute('data-saisies-initialized', 'true');
382
383		// Cacher l'input de date
384		this.dateInput.type = 'hidden';
385		this.dateWrapper.classList.add('saisies-visually-hidden');
386
387		// Afficher les autres inputs et le bouton reset
388		[this.jourInput, this.moisInput, this.anneeInput].forEach((input) => {
389			const wrapper = input.closest('[data-composant]');
390			wrapper.removeAttribute('hidden');
391			wrapper.style.removeProperty('display');
392			if (input.dataset.disabled !== 'true') { // certains sont volontairement en disabled
393				input.removeAttribute('disabled');
394			}
395		});
396		this.resetBtn.style.removeProperty('display');
397		this.resetBtn.removeAttribute('hidden');
398	}
399
400	// Synchronisation d'une saisie : synchroniser la date avec les autres inputs
401	// et gestion du bouton reset
402	sync() {
403		const updateDateValue = () => {
404			const jour = this.jourInput.hasAttribute('disabled') ? '' : this.jourInput.value.trim();
405			const mois = this.moisInput.hasAttribute('disabled') ? '' : this.moisInput.value.trim();
406			const annee = this.anneeInput.hasAttribute('disabled') ? '' : this.anneeInput.value.trim();
407
408			const allEmpty = !jour && !mois && !annee;
409			// const someEmpty = !jour || !mois || !annee;
410
411			if (allEmpty) {
412				this.dateInput.value = '';
413				return;
414			}
415
416			// Normaliser
417			// Si des éléments sont en disabled,
418			// en fallback on s'aligne sur les valeurs minimales tolérées par PHP.
419			const j = jour ? jour.padStart(2, '0') : '01';
420			const m = mois ? mois.padStart(2, '0') : '01';
421			const a = annee ? annee.padStart(4, '0') : '0000';
422
423			let value = `${a}-${m}-${j}`;
424			if (this.format === 'datetime') {
425				value += ' 00:00:00';
426			}
427
428			this.dateInput.value = value;
429			this.dateInput.dispatchEvent(new Event('change', { bubbles: true }));
430		};
431
432		[this.jourInput, this.moisInput, this.anneeInput].forEach(input => {
433			input.addEventListener('input', updateDateValue);
434		});
435
436		// Clic sur le bouton reset
437		if (this.resetBtn) {
438			this.resetBtn.addEventListener('click', (e) => {
439				e.preventDefault();
440				[this.jourInput, this.moisInput, this.anneeInput].forEach(input => {
441					if (
442						!input.hasAttribute('readonly')
443						&& !input.hasAttribute('disabled')
444						&& input.dataset.readonly !== 'true'
445						&& input.dataset.disabled !== 'true'
446				) {
447						input.value = '';
448					}
449				});
450				updateDateValue();
451			});
452		}
453
454		// Synchro initiale s'il y a des valeurs par défaut
455		updateDateValue();
456	}
457}
458// Fonction wrapper pour relancer lors de rechargements ajax
459function saisies_date_jour_mois_annee() {
460	SaisiesDateJourMoisAnnee.init();
461}
462
463/** Ne pas valider lors des retours arrières sur multiétape **/
464function saisies_multi_novalidate() {
465	$('[name^="_retour_etape"],[name="aller_a_etape"]').click(function() {
466		$(this).parents('form').attr('novalidate', 'novalidate');
467	});
468}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.