PageSourceSearch

https://backoffice.motorica.io/assets/js/motorica-form-float.js?v=1790776897

js motorica.io collected 2026-10-02 01:58:21 UTC 12,778 bytes, 461 lines download raw bytes

1/**
2 * Motorica – Float-Label Initialisierung + Legacy-Formular-Migration
3 */
4(function (window, $) {
5	'use strict';
6
7	var SKIP_SELECTOR = [
8		'[data-motorica-no-float]',
9		'.motorica-invoices-filter-card',
10		'.motorica-invoices-date-field',
11		'.customer-search-float',
12		'.motorica-toolbar-row',
13		'.motorica-toolbar-forms',
14		'.dataTables_filter',
15		'.dataTables_length',
16		'.dt-length',
17		'.dt-search',
18		'.form-check',
19		'.form-check-inline',
20		'.custom-control',
21		'.custom-file',
22		'.input-group',
23		'.motorica-image-upload',
24		'.btn-group',
25		'.select2-container',
26		'.calendar-picker-wrap',
27		'.doc-form-section--discount',
28	].join(', ');
29
30	var CONTROL_SELECTOR = 'input.form-control:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]), select.form-control, textarea.form-control';
31
32	function selectFloatAlwaysActive(select) {
33		return select && (select.dataset.motoricaFloatAlways === '1'
34			|| select.getAttribute('data-motorica-float-always') === '1');
35	}
36
37	function syncSelectHasValue(select) {
38		if (!select || select.tagName !== 'SELECT') {
39			return;
40		}
41		if (selectFloatAlwaysActive(select)) {
42			select.classList.add('has-value');
43			return;
44		}
45		var val = select.value;
46		if (val !== null && String(val).trim() !== '') {
47			select.classList.add('has-value');
48		} else {
49			select.classList.remove('has-value');
50		}
51	}
52
53	function syncReadonlyFloat(input) {
54		if (!input || !input.readOnly) {
55			return;
56		}
57		if (String(input.value || '').trim() !== '') {
58			input.setAttribute('placeholder', ' ');
59		}
60	}
61
62	function syncInputHasValue(input) {
63		if (!input || input.tagName !== 'INPUT') {
64			return;
65		}
66		var type = (input.getAttribute('type') || 'text').toLowerCase();
67		if (type === 'checkbox' || type === 'radio' || type === 'hidden' || type === 'file') {
68			return;
69		}
70		if (type === 'number' || type === 'range') {
71			if (input.value !== '' && input.value !== null) {
72				input.classList.add('has-value');
73			} else {
74				input.classList.remove('has-value');
75			}
76			return;
77		}
78		if (String(input.value || '').trim() !== '') {
79			input.classList.add('has-value');
80		} else {
81			input.classList.remove('has-value');
82		}
83	}
84
85	function normalizeFloatLabel(label, floatWrap, container) {
86		if (!label) {
87			return;
88		}
89		var extraNodes = [];
90		var br = label.querySelector('br');
91		if (br) {
92			var node = br.nextSibling;
93			while (node) {
94				var next = node.nextSibling;
95				extraNodes.push(node);
96				node = next;
97			}
98			br.remove();
99			extraNodes.forEach(function (n) {
100				if (n.parentNode === label) {
101					label.removeChild(n);
102				}
103			});
104		}
105		if (extraNodes.length) {
106			var sub = document.createElement('div');
107			sub.className = 'motorica-field-sublabel';
108			extraNodes.forEach(function (n) {
109				sub.appendChild(n);
110			});
111			if (floatWrap.nextSibling) {
112				container.insertBefore(sub, floatWrap.nextSibling);
113			} else {
114				container.appendChild(sub);
115			}
116		}
117		var text = (label.textContent || '').replace(/\s+/g, ' ').trim();
118		if (text) {
119			label.textContent = text;
120		}
121	}
122
123	function shouldSkipContainer(container) {
124		if (!container || container.closest(SKIP_SELECTOR)) {
125			return true;
126		}
127		if (container.closest('[data-motorica-no-float]')) {
128			return true;
129		}
130		if (container.querySelector('.form-check, .form-check-inline, .custom-control, .custom-file')) {
131			return true;
132		}
133		return false;
134	}
135
136	function findLabelForControl(container, control) {
137		if (!control || !control.id) {
138			return null;
139		}
140		var label = container.querySelector(':scope > label[for="' + control.id + '"]');
141		if (label) {
142			return label;
143		}
144		label = container.querySelector('label[for="' + control.id + '"]:not(.float-label)');
145		return label;
146	}
147
148	function findPrimaryControl(container) {
149		var direct = container.querySelector(':scope > ' + CONTROL_SELECTOR);
150		if (direct) {
151			return direct;
152		}
153		return container.querySelector(CONTROL_SELECTOR);
154	}
155
156	function isSelect2Candidate(select) {
157		if (!select || select.tagName !== 'SELECT') {
158			return false;
159		}
160		if (select.classList.contains('select2-hidden-accessible')) {
161			return true;
162		}
163		if (select.classList.contains('motorica-select2-source')) {
164			return true;
165		}
166		if (select.dataset.select2Id !== undefined) {
167			return true;
168		}
169		if (select.classList.contains('select2')) {
170			return true;
171		}
172		return false;
173	}
174
175	function prepareControl(control) {
176		control.classList.remove('form-control-lg', 'form-control-sm');
177		if (control.tagName === 'INPUT') {
178			var type = (control.getAttribute('type') || 'text').toLowerCase();
179			if (type !== 'date' && type !== 'color' && type !== 'time' && type !== 'datetime-local') {
180				if (!control.getAttribute('placeholder')) {
181					control.setAttribute('placeholder', ' ');
182				}
183			}
184		}
185		if (control.tagName === 'TEXTAREA' && !control.getAttribute('placeholder')) {
186			control.setAttribute('placeholder', ' ');
187		}
188	}
189
190	function destroySelect2IfNeeded(control) {
191		if (!$ || !$.fn || typeof $.fn.select2 !== 'function') {
192			return null;
193		}
194		var $control = $(control);
195		if (!$control.hasClass('select2-hidden-accessible')) {
196			return null;
197		}
198		var data = $control.data('select2');
199		var options = {};
200		if (data && data.options) {
201			options = $.extend({}, data.options);
202		}
203		$control.select2('destroy');
204		return options;
205	}
206
207	function reinitSelect2(control, previousOptions) {
208		if (!window.MotoricaFormSelect2 || typeof window.MotoricaFormSelect2.init !== 'function') {
209			return;
210		}
211		window.MotoricaFormSelect2.init($(control), previousOptions || {});
212	}
213
214	function wrapControlAsFloat(container, control, label) {
215		if (!control || control.closest('.form-float')) {
216			return false;
217		}
218		if (label && label.classList.contains('float-label')) {
219			return false;
220		}
221
222		var select2Options = null;
223		if (control.tagName === 'SELECT' && isSelect2Candidate(control)) {
224			select2Options = destroySelect2IfNeeded(control);
225		}
226
227		var floatWrap = document.createElement('div');
228		floatWrap.className = 'form-float';
229		if (control.tagName === 'TEXTAREA') {
230			floatWrap.classList.add('form-float--textarea');
231		}
232		if (control.tagName === 'SELECT' && (isSelect2Candidate(control) || control.classList.contains('motorica-select2'))) {
233			floatWrap.classList.add('form-float--select2');
234		} else if (control.tagName === 'SELECT') {
235			floatWrap.classList.add('form-float--select-native');
236		}
237
238		if (label) {
239			label.parentNode.insertBefore(floatWrap, label);
240		} else {
241			container.insertBefore(floatWrap, control);
242		}
243
244		floatWrap.appendChild(control);
245		prepareControl(control);
246
247		if (label) {
248			normalizeFloatLabel(label, floatWrap, container);
249			label.classList.add('float-label');
250			label.classList.remove('col-form-label', 'sr-only');
251			floatWrap.appendChild(label);
252		}
253
254		container.classList.add('motorica-field-group', 'motorica-float-migrated');
255		container.dataset.motoricaFloatMigrated = '1';
256
257		if (control.tagName === 'SELECT') {
258			syncSelectHasValue(control);
259		}
260		syncInputHasValue(control);
261		syncReadonlyFloat(control);
262
263		if (select2Options !== null) {
264			reinitSelect2(control, select2Options);
265		}
266
267		return true;
268	}
269
270	function migrateFormGroup(group) {
271		if (!group || group.dataset.motoricaFloatMigrated === '1') {
272			return false;
273		}
274		if (shouldSkipContainer(group)) {
275			return false;
276		}
277		if (group.querySelector('.form-float')) {
278			group.dataset.motoricaFloatMigrated = '1';
279			group.classList.add('motorica-field-group');
280			return false;
281		}
282
283		var control = findPrimaryControl(group);
284		if (!control) {
285			return false;
286		}
287		var label = findLabelForControl(group, control);
288		if (!label) {
289			return false;
290		}
291		if (label.classList.contains('sr-only')) {
292			return false;
293		}
294
295		return wrapControlAsFloat(group, control, label);
296	}
297
298	function migrateLabeledBlock(block) {
299		if (!block || block.dataset.motoricaFloatMigrated === '1') {
300			return false;
301		}
302		if (shouldSkipContainer(block)) {
303			return false;
304		}
305		if (!block.classList.contains('mb-3') && !block.classList.contains('mb-2') && !block.classList.contains('mb-4')) {
306			return false;
307		}
308		if (block.querySelector('.form-float')) {
309			return false;
310		}
311		var control = findPrimaryControl(block);
312		if (!control) {
313			return false;
314		}
315		var label = findLabelForControl(block, control);
316		if (!label) {
317			return false;
318		}
319		return wrapControlAsFloat(block, control, label);
320	}
321
322	function migrateRoot(root) {
323		if (!root || !root.querySelectorAll) {
324			return;
325		}
326
327		root.querySelectorAll('.form-group:not([data-motorica-float-migrated])').forEach(function (group) {
328			migrateFormGroup(group);
329		});
330
331		root.querySelectorAll('.mb-3:not([data-motorica-float-migrated]), .mb-2:not([data-motorica-float-migrated]), .mb-4:not([data-motorica-float-migrated])').forEach(function (block) {
332			migrateLabeledBlock(block);
333		});
334
335		root.querySelectorAll('.form-float--select2 select.form-control, select.motorica-select2, select[data-motorica-select2="1"]').forEach(function (select) {
336			var wrap = select.closest('.form-float');
337			if (wrap && !wrap.classList.contains('form-float--select2')) {
338				wrap.classList.add('form-float--select2');
339			}
340			if (isSelect2Candidate(select) && !select.classList.contains('select2-hidden-accessible')) {
341				reinitSelect2(select, {});
342			}
343		});
344	}
345
346	function init(scope) {
347		var root = scope && scope.querySelector ? scope : document;
348		if (typeof scope === 'string') {
349			root = document.querySelector(scope) || document;
350		}
351
352		migrateRoot(root);
353
354		root.querySelectorAll('select.form-control').forEach(function (select) {
355			syncSelectHasValue(select);
356			if (!select.dataset.motoricaFloatSelectBound) {
357				select.dataset.motoricaFloatSelectBound = '1';
358				select.addEventListener('change', function () {
359					syncSelectHasValue(select);
360				});
361			}
362		});
363
364		root.querySelectorAll('.form-float input.form-control, .form-float input[type="text"], .form-float input[type="email"], .form-float input[type="tel"], .form-float input[type="number"], .form-float input[type="password"], .form-float input[type="date"], .form-float input[type="time"], .form-float input[type="datetime-local"]').forEach(function (input) {
365			syncInputHasValue(input);
366			if (!input.dataset.motoricaFloatInputBound) {
367				input.dataset.motoricaFloatInputBound = '1';
368				input.addEventListener('input', function () {
369					syncInputHasValue(input);
370				});
371				input.addEventListener('change', function () {
372					syncInputHasValue(input);
373				});
374			}
375		});
376
377		root.querySelectorAll('.form-float input[readonly], .form-float .motorica-settings-readonly').forEach(syncReadonlyFloat);
378
379		root.querySelectorAll('input[type="date"].form-control').forEach(function (input) {
380			if (String(input.value || '').trim() !== '' && !input.getAttribute('placeholder')) {
381				input.setAttribute('placeholder', ' ');
382			}
383		});
384
385		try {
386			document.dispatchEvent(new CustomEvent('motorica:form-float-ready', { detail: { root: root } }));
387		} catch (e) {
388			/* IE11 fallback not required */
389		}
390	}
391
392	function ensureScopes() {
393		var wrappers = document.querySelectorAll('#content-wrapper, .tableBackground, .login-content-wrap, .motorica-form-float-scope');
394		wrappers.forEach(function (el) {
395			if (!el.classList.contains('motorica-form-float-scope')) {
396				el.classList.add('motorica-form-float-scope');
397			}
398		});
399	}
400
401	function boot() {
402		ensureScopes();
403		document.querySelectorAll('.motorica-form-float-scope').forEach(function (scope) {
404			init(scope);
405		});
406	}
407
408	var observerStarted = false;
409
410	function startObserver() {
411		if (observerStarted || typeof MutationObserver === 'undefined') {
412			return;
413		}
414		observerStarted = true;
415		var timer = null;
416		var observer = new MutationObserver(function (mutations) {
417			var needsInit = false;
418			mutations.forEach(function (mutation) {
419				if (mutation.addedNodes && mutation.addedNodes.length) {
420					needsInit = true;
421				}
422			});
423			if (!needsInit) {
424				return;
425			}
426			clearTimeout(timer);
427			timer = setTimeout(function () {
428				document.querySelectorAll('.motorica-form-float-scope').forEach(function (scope) {
429					init(scope);
430				});
431			}, 50);
432		});
433		observer.observe(document.body, { childList: true, subtree: true });
434	}
435
436	function scheduleBoot() {
437		if (document.readyState === 'loading') {
438			document.addEventListener('DOMContentLoaded', function () {
439				boot();
440				startObserver();
441			});
442		} else {
443			boot();
444			startObserver();
445		}
446	}
447
448	window.MotoricaFormFloat = {
449		init: init,
450		migrate: migrateRoot,
451		syncSelect: syncSelectHasValue,
452		syncSelect2: function (select) {
453			syncSelectHasValue(select);
454			if ($ && window.MotoricaFormSelect2 && typeof window.MotoricaFormSelect2.sync === 'function') {
455				window.MotoricaFormSelect2.sync($(select));
456			}
457		},
458	};
459
460	scheduleBoot();
461})(window, window.jQuery);

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.