PageSourceSearch

https://niravadya.com/wp-content/plugins/civi-framework/assets/js/flatpickr-init.js?ver=2.2.7

js niravadya.com collected 2026-10-01 23:56:00 UTC 16,523 bytes, 667 lines download raw bytes

1"use strict";
2
3(function($) {
4	if (typeof $ === "undefined" || typeof window.jQuery === "undefined") {
5		return;
6	}
7
8	// Constants
9	const DEFAULTS = {
10		LOCALE: "en",
11		DATE_FORMAT: "Y-m-d",
12		SELECTOR: ".datepicker, input[type='date']",
13		INIT_DELAY: 100,
14		RETRY_DELAY: 100,
15		DEBOUNCE_DELAY: 150
16	};
17
18	const DATE_FORMATS = {
19		ISO: "Y-m-d",
20		DMY_SLASH: "d/m/Y",
21		MDY_SLASH: "m/d/Y",
22		DMY_DOT: "d.m.Y",
23		DMY_DASH: "d-m-Y",
24		US_LONG: "F j, Y",
25		JP_CN: "Y年m月d日",
26		KO: "Y년 m월 d일"
27	};
28
29	// Locale groups để giảm repetition
30	const LOCALE_GROUPS = {
31		CJK: ["zh", "zh-cn", "zh-tw", "zh_CN", "zh_TW", "ja"],
32		KOREAN: ["ko", "ko-kr", "ko_KR"],
33		DMY_SLASH: ["vi", "vn", "th", "id", "fr", "es", "it", "pt", "tr", "ar", "he", "af", "sw", "bn"],
34		DMY_DOT: ["de", "ru", "pl"],
35		DMY_DASH: ["nl"],
36		SPANISH: ["es-mx", "es-ar", "es-co", "es-cl", "es-pe", "es-ve", "es-cr", "es-ec", "es-gt", "es-pa", "es-do", "es-cu", "es-bo", "es-hn", "es-ni", "es-py", "es-sv", "es-uy"],
37		PORTUGUESE: ["pt-br", "pt-pt", "pt_BR", "pt_PT"],
38		AFRICAN: ["af-za", "zu", "xh", "en-ng", "en_NG", "en-zw", "en_ZW"],
39		BENGALI: ["bn-bd", "bn_BD", "bd"]
40	};
41
42	/**
43	 * Utility functions
44	 */
45	const Utils = {
46		/**
47		 * Debounce function để tránh call nhiều lần
48		 */
49		debounce: function(func, wait) {
50			let timeout;
51			return function executedFunction(...args) {
52				const later = () => {
53					clearTimeout(timeout);
54					func(...args);
55				};
56				clearTimeout(timeout);
57				timeout = setTimeout(later, wait);
58			};
59		},
60
61		/**
62		 * Get locale code từ nhiều nguồn
63		 */
64		getLocaleConfig: function() {
65			try {
66				// Priority 1: URL params
67				const urlLocale = this.getUrlParam("flatpickr_locale");
68				if (urlLocale) {
69					return { locale: urlLocale, source: "url" };
70				}
71
72				// Priority 2: civi_flatpickr_vars
73				if (typeof civi_flatpickr_vars !== "undefined") {
74					return {
75						locale: civi_flatpickr_vars.locale || DEFAULTS.LOCALE,
76						wpLocale: civi_flatpickr_vars.wp_locale,
77						dateFormat: civi_flatpickr_vars.date_format || DEFAULTS.DATE_FORMAT,
78						source: "flatpickr_vars"
79					};
80				}
81
82				// Priority 3: civi_template_vars
83				if (typeof civi_template_vars !== "undefined") {
84					return {
85						locale: civi_template_vars.flatpickr_locale || DEFAULTS.LOCALE,
86						wpLocale: civi_template_vars.wp_locale,
87						dateFormat: civi_template_vars.date_format || DEFAULTS.DATE_FORMAT,
88						source: "template_vars"
89					};
90				}
91
92				// Priority 4: Browser locale
93				const browserLocale = (navigator.language || navigator.userLanguage || "en").substring(0, 2);
94				return {
95					locale: browserLocale,
96					wpLocale: browserLocale,
97					dateFormat: DEFAULTS.DATE_FORMAT,
98					source: "browser"
99				};
100			} catch (e) {
101				console.error("CiviFlatpickr: Error reading locale config", e);
102				return {
103					locale: DEFAULTS.LOCALE,
104					dateFormat: DEFAULTS.DATE_FORMAT,
105					source: "fallback"
106				};
107			}
108		},
109
110		/**
111		 * Get URL parameter
112		 */
113		getUrlParam: function(name) {
114			try {
115				if (typeof URLSearchParams !== "undefined") {
116					const urlParams = new URLSearchParams(window.location.search);
117					return urlParams.get(name);
118				}
119				const match = window.location.search.match(new RegExp(`[?&]${name}=([^&]*)`));
120				return match ? match[1] : null;
121			} catch (e) {
122				return null;
123			}
124		},
125
126		/**
127		 * Check element should be initialized
128		 */
129		shouldInitElement: function($el) {
130			return !$el.data("flatpickr") &&
131				   !$el.data("flatpickr-initialized") &&
132				   !$el.hasClass("no-flatpickr");
133		},
134
135		/**
136		 * Parse date từ string
137		 */
138		parseDate: function(dateStr) {
139			if (!dateStr) return null;
140
141			try {
142				const dateObj = new Date(dateStr);
143				return !isNaN(dateObj.getTime()) ? dateObj : null;
144			} catch (e) {
145				return null;
146			}
147		}
148	};
149
150	/**
151	 * Locale Manager - xử lý locale và format
152	 */
153	const LocaleManager = {
154		/**
155		 * Lấy format dựa trên locale
156		 */
157		getFormatByLocale: function(localeCode) {
158			// Normalize locale code
159			const normalizedLocale = localeCode.toLowerCase();
160
161			// CJK locales
162			if (LOCALE_GROUPS.CJK.some(l =>
162 normalizedLocale.startsWith(l))) {
163				return DATE_FORMATS.JP_CN;
164			}
165
166			// Korean
167			if (LOCALE_GROUPS.KOREAN.some(l => normalizedLocale.startsWith(l))) {
168				return DATE_FORMATS.KO;
169			}
170
171			// DMY with slash
172			if (LOCALE_GROUPS.DMY_SLASH.some(l => normalizedLocale.startsWith(l)) ||
173				LOCALE_GROUPS.SPANISH.some(l => normalizedLocale === l) ||
174				LOCALE_GROUPS.PORTUGUESE.some(l => normalizedLocale === l) ||
175				LOCALE_GROUPS.AFRICAN.some(l => normalizedLocale === l) ||
176				LOCALE_GROUPS.BENGALI.some(l => normalizedLocale === l)) {
177				return DATE_FORMATS.DMY_SLASH;
178			}
179
180			// DMY with dot
181			if (LOCALE_GROUPS.DMY_DOT.some(l => normalizedLocale.startsWith(l))) {
182				return DATE_FORMATS.DMY_DOT;
183			}
184
185			// DMY with dash
186			if (LOCALE_GROUPS.DMY_DASH.some(l => normalizedLocale.startsWith(l))) {
187				return DATE_FORMATS.DMY_DASH;
188			}
189
190			// Default
191			return DATE_FORMATS.ISO;
192		},
193
194		/**
195		 * Get flatpickr locale object
196		 */
197		getFlatpickrLocale: function(localeCode, wpLocale) {
198			try {
199				if (localeCode === "en" || typeof flatpickr.l10ns === "undefined") {
200					return null;
201				}
202
203				// Direct match
204				if (flatpickr.l10ns[localeCode]) {
205					return flatpickr.l10ns[localeCode];
206				}
207
208				// Chinese variations
209				if (localeCode === "zh") {
210					return flatpickr.l10ns["zh-cn"] || flatpickr.l10ns["zh_tw"] || null;
211				}
212
213				// Language family fallback
214				const baseLocale = localeCode.split(/[-_]/)[0];
215				if (flatpickr.l10ns[baseLocale]) {
216					return flatpickr.l10ns[baseLocale];
217				}
218
219				// Try wpLocale
220				if (wpLocale && flatpickr.l10ns[wpLocale]) {
221					return flatpickr.l10ns[wpLocale];
222				}
223
224				return null;
225			} catch (e) {
226				console.warn("CiviFlatpickr: Error loading locale", localeCode, e);
227				return null;
228			}
229		},
230
231		/**
232		 * Convert WP date format to Flatpickr format
233		 */
234		convertDateFormat: function(wpFormat) {
235			if (!wpFormat) return DATE_FORMATS.ISO;
236
237			// Common formats mapping
238			const formatMap = {
239				"F j, Y": DATE_FORMATS.US_LONG,
240				"Y-m-d": DATE_FORMATS.ISO,
241				"Y/m/d": DATE_FORMATS.ISO,
242				"d/m/Y": DATE_FORMATS.DMY_SLASH,
243				"d-m-Y": DATE_FORMATS.DMY_DASH,
244				"m/d/Y": DATE_FORMATS.MDY_SLASH,
245				"m-d-Y": DATE_FORMATS.MDY_SLASH,
246				"d.m.Y": DATE_FORMATS.DMY_DOT
247			};
248
249			if (formatMap[wpFormat]) {
250				return formatMap[wpFormat];
251			}
252
253			// Detect separator và order
254			if (wpFormat.includes("/")) {
255				return this.detectFormatOrder(wpFormat, "/");
256			} else if (wpFormat.includes("-")) {
257				return this.detectFormatOrder(wpFormat, "-");
258			} else if (wpFormat.includes(".")) {
259				return DATE_FORMATS.DMY_DOT;
260			}
261
262			return DATE_FORMATS.ISO;
263		},
264
265		/**
266		 * Detect date format order (YMD, DMY, MDY)
267		 */
268		detectFormatOrder: function(format, separator) {
269			const yPos = format.indexOf("Y");
270			const mPos = format.indexOf("m");
271			const dPos = format.indexOf("d");
272
273			if (yPos < mPos && mPos < dPos) {
274				return `Y${separator}m${separator}d`;
275			} else if (dPos < mPos && mPos < yPos) {
276				return `d${separator}m${separator}Y`;
277			} else if (mPos < dPos && dPos < yPos) {
278				return `m${separator}d${separator}Y`;
279			}
280
281			return DATE_FORMATS.ISO;
282		},
283
284		/**
285		 * Get final date format
286		 */
287		getDateFormat: function(options) {
288			const { localeCode, wpLocale, wpDateFormat, localeObj } = options;
289
290			// Priority 1: Locale object has format
291			if (localeObj?.dateFormat) {
292				return localeObj.dateFormat;
293			}
294
295			// Priority 2: Locale-specific format
296			const localeFormat = this.getFormatByLocale(localeCode);
297
298			// Priority 3: For CJK languages, always use their format
299			if (localeFormat === DATE_FORMATS.JP_CN || localeFormat === DATE_FORMATS.KO) {
300				return localeFormat;
301			}
302
303			// Priority 4: Convert WP format
304			const convertedFormat = this.convertDateFormat(wpDateFormat);
305
306			// Priority 5: US locale with F j, Y format
307			if (wpDateFormat === "F j, Y" && ["en", "en_US", "en_GB"].includes(localeCode)) {
308				return DATE_FORMATS.US_LONG;
309			}
310
311			// Use converted format if compatible, otherwise use locale format
312			return this.isFormatCompatible(wpDateFormat, localeFormat)
313				? convertedFormat
314				: localeFormat;
315		},
316
317		/**
318		 * Check if formats are compatible
319		 */
320		isFormatCompatible: function(wpFormat, localeFormat) {
321			// ISO vs DMY/MDY incompatible
322			if (wpFormat === "Y-m-d" && (localeFormat.includes("d/m") || localeFormat.includes("m/d"))) {
323				return false;
324			}
325
326			// DMY/MDY vs ISO incompatible
327			if ((wpFormat.includes("d/m") || wpFormat.includes("m/d")) && localeFormat === "Y-m-d") {
328				return false;
329			}
330
331			// CJK formats
332			if (wpFormat.includes("年") || wpFormat.includes("월")) {
333				return true;
334			}
335
336			return true;
337		}
338	};
339
340	/**
341	 * Main CiviFlatpickr object
342	 */
343	window.CiviFlatpickr = {
344		initialized: false,
345		initInProgress: false,
346
347		/**
348		 * Auto initialize
349		 */
350		autoInit: function() {
351			// Check if flatpickr library loaded
352			if (typeof flatpickr === "undefined") {
353				if (!this.waitTimeout) {
354					this.waitTimeout = setTimeout(() => {
355						this.waitTimeout = null;
356						this.autoInit();
357					}, DEFAULTS.RETRY_DELAY);
358				}
359				return;
360			}
361
362			const config = Utils.getLocaleConfig();
363			this.init({
364				localeCode: config.locale,
365				wpLocale: config.wpLocale,
366				wpDateFormat: config.dateFormat || DEFAULTS.DATE_FORMAT,
367				selector: DEFAULTS.SELECTOR
368			});
369		},
370
371		/**
372		 * Initialize flatpickr
373		 */
374		init: function(options) {
375			// Validation
376			if (typeof flatpickr === "undefined") {
377				console.warn("CiviFlatpickr: flatpickr library not loaded");
378				return;
379			}
380
381			if (typeof $ === "undefined") {
382				console.warn("CiviFlatpickr: jQuery not loaded");
383				return;
384			}
385
386			// Prevent concurrent init
387			if (this.initInProgress) {
388				return;
389			}
390
391			this.initInProgress = true;
392
393			try {
394				// Merge options
395				const defaultOptions = {
396					localeCode: DEFAULTS.LOCALE,
397					wpLocale: DEFAULTS.LOCALE,
398					wpDateFormat: DEFAULTS.DATE_FORMAT,
399					selector: DEFAULTS.SELECTOR
400				};
401
402				options = $.extend({}, defaultOptions, options || {});
403
404				// Get locale object
405				const localeObj = LocaleManager.getFlatpickrLocale(
406					options.localeCode,
407					options.wpLocale
408				);
409
410				// Get date format
411				const dateFormat = LocaleManager.getDateFormat({
412					localeCode: options.localeCode,
413					wpLocale: options.wpLocale,
414					wpDateFormat: options.wpDateFormat,
415					localeObj: localeObj
416				});
417
418				// Build flatpickr config
419				const flatpickrConfig = this.buildFlatpickrConfig({
420					dateFormat,
421					localeObj,
422					minDate: options.minDate,
423					maxDate: options.maxDate
424				});
425
426				// Initialize elements
427				this.initializeElements(options.selector, flatpickrConfig);
428
429			} catch (e) {
430				console.error("CiviFlatpickr: Error in init", e);
431			} finally {
432				this.initInProgress = false;
433			}
434		},
435
436		/**
437		 * Build flatpickr configuration
438		 */
439		buildFlatpickrConfig: function(options) {
440			const { dateFormat, localeObj, minDate, maxDate } = options;
441
442			const config = {
443				dateFormat: dateFormat,
444				allowInput: true,
445				clickOpens: true,
446				animate: true,
447				monthSelectorType: "static",
448				onChange: this.createOnChangeHandler(),
449				onReady: this.createOnReadyHandler()
450			};
451
452			// Add locale
453			if (localeObj) {
454				config.locale = localeObj;
455
456				if (localeObj.rtl === true) {
457					config.wrap = true;
458					config.position = "auto";
459				}
460			}
461
462			// Add date constraints
463			if (minDate) config.minDate = minDate;
464			if (maxDate) config.maxDate = maxDate;
465
466			return config;
467		},
468
469		/**
470		 * Create onChange callback
471		 */
472		createOnChangeHandler: function() {
473			return function(selectedDates, dateStr, instance) {
474				try {
475					const $input = $(instance.input);
476					if ($input.length) {
477						$input.val(dateStr).trigger("change");
478					}
479				} catch (e) {
480					console.warn("CiviFlatpickr: Error in onChange", e);
481				}
482			};
483		},
484
485		/**
486		 * Create onReady callback
487		 */
488		createOnReadyHandler: function() {
489			return function(selectedDates, dateStr, instance) {
490				try {
491					const $input = $(instance.input);
492					if (!$input.length) return;
493
494					$input.data("flatpickr-initialized", true);
495
496					// Set initial value if exists
497					const originalValue = $input.attr('value') || $input.data('debug-value');
498					if (originalValue && (!selectedDates || selectedDates.length === 0)) {
499						setTimeout(() => {
500							const dateObj = Utils.parseDate(originalValue);
501							if (dateObj) {
502								instance.setDate(dateObj, true);
503							}
504						}, DEFAULTS.INIT_DELAY);
505					}
506				} catch (e) {
507					console.warn("CiviFlatpickr: Error in onReady", e);
508				}
509			};
510		},
511
512		/**
513		 * Initialize elements
514		 */
515		initializeElements: function(selector, baseConfig) {
516			try {
517				const $elements = $(selector).filter(function() {
518					return Utils.shouldInitElement($(this));
519				});
520
521				if ($elements.length === 0) {
522					return;
523				}
524
525				$elements.each((index, element) => {
526					try {
527						this.initializeSingleElement(element, baseConfig);
528					} catch (e) {
529						console.warn("CiviFlatpickr: Error initializing element", element, e);
530					}
531				});
532			} catch (e) {
533				console.error("CiviFlatpickr: Error in initializeElements", e);
534			}
535		},
536
537		/**
538		 * Initialize single element
539		 */
540		initializeSingleElement: function(element, baseConfig) {
541			const $input = $(element);
542			const config = { ...baseConfig };
543
544			// Handle input[type="date"]
545			if ($input.attr("type") === "date") {
546				const minDate = $input.attr("min");
547				const maxDate = $input.attr("max");
548
549				$input.attr("type", "text").addClass("datepicker");
550
551				if (minDate) config.minDate = minDate;
552				if (maxDate) config.maxDate = maxDate;
553			}
554
555			// Set default date
556			const inputValue = $input.val();
557			if (inputValue) {
558				config.defaultDate = inputValue;
559			}
560
561			// Mobile / touch: native picker path can break inside overflow/transform; use flatpickr on body.
562			const mobileLike =
563				typeof window.matchMedia === "function" &&
564				(window.matchMedia("(max-width: 782px)").matches ||
565					window.matchMedia("(pointer: coarse)").matches);
566			if (mobileLike && typeof document !== "undefined" && document.body) {
567				config.disableMobile = true;
568				config.appendTo = document.body;
569			}
570
571			// Initialize flatpickr
572			const fpInstance = flatpickr(element, config);
573
574			if (fpInstance) {
575				$input.data("flatpickr", fpInstance);
576
577				// Set value if not already set
578				if (inputValue && !fpInstance.selectedDates.length) {
579					setTimeout(() => {
580						const dateObj = Utils.parseDate(inputValue);
581						if (dateObj) {
582							fpInstance.setDate(dateObj, true);
583						}
584					}, DEFAULTS.INIT_DELAY);
585				}
586			}
587		}
588	};
589
590	/**
591	 * DOM Observer - Quản lý việc observe DOM changes
592	 */
593	const DOMObserver = {
594		observer: null,
595		debouncedInit: null,
596
597		init: function() {
598			if (typeof MutationObserver === "undefined") {
599				console.warn("CiviFlatpickr: MutationObserver not supported");
600				return;
601			}
602
603			// Create debounced init function
604			this.debouncedInit = Utils.debounce(() => {
605				if (typeof window.CiviFlatpickr !== "undefined") {
606					window.CiviFlatpickr.autoInit();
607				}
608			}, DEFAULTS.DEBOUNCE_DELAY);
609
610			// Create observer
611			this.observer = new MutationObserver((mutations) => {
612				if (this.shouldInit(mutations)) {
613					this.debouncedInit();
614				}
615			});
616
617			// Start observing
618			this.observer.observe(document.body, {
619				childList: true,
620				subtree: true
621			});
622		},
623
624		shouldInit: function(mutations) {
625			return mutations.some(mutation => {
626				if (!mutation.addedNodes.length) return false;
627
628				return Array.from(mutation.addedNodes).some(node => {
629					if (node.nodeType !== 1) return false;
630
631					const $node = $(node);
632					return $node.is(DEFAULTS.SELECTOR) ||
633						   $node.find(DEFAULTS.SELECTOR).length > 0;
634				});
635			});
636		},
637
638		disconnect: function() {
639			if (this.observer) {
640				this.observer.disconnect();
641			}
642		}
643	};
644
645	/**
646	 * Initialize on document ready
647	 */
648	$(document).ready(function() {
649		// Initial init
650		if (typeof window.CiviFlatpickr !== "undefined") {
651			window.CiviFlatpickr.autoInit();
652		}
653
654		// Setup DOM observer
655		DOMObserver.init();
656
657		// Handle AJAX complete
658		const debouncedAjaxInit = Utils.debounce(() => {
659			if (typeof window.CiviFlatpickr !== "undefined") {
660				window.CiviFlatpickr.autoInit();
661			}
662		}, DEFAULTS.DEBOUNCE_DELAY);
663
664		$(document).on("ajaxComplete", debouncedAjaxInit);
665	});
666
667})(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.