/** * Ticket forms + claim file upload: charset, email, pincode, mobile, URLs, doc names. * Integrates with Parsley only — no separate Bootstrap invalid-feedback. * Does not strip or rewrite user input; invalid values are reported via Parsley messages only. * Requires jQuery + Parsley. Inits run before form-validation.init binds .parsley-examples. * * Do not capture window.jQuery at parse time: layout/header.php loads full jQuery then jquery.slim, * which replaces window.jQuery; Parsley (footer) binds $.fn.parsley to the final jQuery. Always use getJq(). */ (function (getJq) { 'use strict'; /** Live jQuery — must not close over an outdated window.jQuery (e.g. slim vs full). */ function $(sel, context) { var jQ = getJq(); if (!jQ) { return { length: 0 }; } return arguments.length > 1 ? jQ(sel, context) : jQ(sel); } var TEXT_ALLOWED = /^[A-Za-z0-9/_.\- ]*$/; /** Matches `TicketController::upload_url` rules for `docs_name.*` */ var DOCNAME_ALLOWED = /^[a-zA-Z0-9_\- ]+$/; var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; var EMAIL_IDS = ['emp_mail']; var PERSONAL_MAIL_IDS = ['emp_personal_mail']; var MOBILE_IDS = ['emp_mobile', 'hospital_phone_no']; var PINCODE_IDS = ['hospital_pin_code']; var DIGITS_ONLY_IDS = ['claim_amount', 'approved_amount', 'si_amt']; /** Same validation as claim upload URL fields — charset rules would strip : ? & from https links. */ var APPROVED_SETTLE_LETTER_URL_IDS = ['approved_letter', 'settle_letter']; var MESSAGES = { text: 'Only letters, numbers, spaces, and the characters / _ - . are allowed.', email: 'Please enter a valid email address.', mobile: 'Mobile number must be exactly 10 digits (numbers only).', pincode: 'Pincode must be exactly 6 digits (numbers only).', digits: 'Only numbers are allowed.', docname: 'Document name can only contain letters, numbers, spaces, hyphens, and underscores.', claimurl: 'Enter a valid web address (e.g. https://example.com/path?x=1 or example.com).', claimfile: 'Only PDF, JPG, JPEG, and PNG files are allowed.' }; function isReadonly(el) { return el.readOnly === true || $(el).attr('readonly') !== undefined; } /** * Resolve validation kind by element id and/or name (supports docs_name[], url[] on claim upload). */ function getFieldKindFromElement(el) { if (!el) { return 'text'; } var id = el.id || ''; var name = (el.name || '').replace(/\[\]$/, ''); if (el.type === 'file' && /^file_upload_/.test(id)) { return 'claimfile'; } if (name === 'docs_name' || name === 'ir_document_name' || /^docs_name_/.test(id) || /^ir_doc_name_/.test(id) || id === 'edit_doc_name') { return 'docname'; } if (name === 'url' || /^url_name_/.test(id) || id === 'edit_url_link') { return 'urlfield'; } if (!id) { return 'text'; } if (EMAIL_IDS.indexOf(id) !== -1) { return 'email'; } if (PERSONAL_MAIL_IDS.indexOf(id) !== -1) { return 'personalemail'; } if (MOBILE_IDS.indexOf(id) !== -1) { return 'mobile'; } if (PINCODE_IDS.indexOf(id) !== -1) { return 'pincode'; } if (DIGITS_ONLY_IDS.indexOf(id) !== -1) { return 'digits'; } if (APPROVED_SETTLE_LETTER_URL_IDS.indexOf(id) !== -1) { return 'urlfield'; } return 'text'; } /** * Loose http(s) URL check aligned with `TicketController::isClaimUploadUrl` — allows paths, ?query=, ports, longer TLDs. */ function isClaimUploadUrlString(value) { var v = String(value || '').trim(); if (!v.length) { return true; } if (v.length > 2048) { return false; } var raw = v; if (!/^https?:\/\//i.test(raw)) { raw = 'https://' + raw; } try { var u = new URL(raw); if (u.protocol !== 'http:' && u.protocol !== 'https:') { return false; } var h = (u.hostname || '').toLowerCase(); if (!h.length) { return false; } if (h === 'localhost') { return true; } if (/^(\d{1,3}\.){3}\d{1,3}$/.test(h)) { return true; } if (h.indexOf(':') !== -1) { return true; } return h.indexOf('.') !== -1; } catch (e) { return false; } } function registerParsleyTicketValidators() { if (!window.Parsley || window.Parsley.__ticketFormValidatorsRegistered) { return; } window.Parsley.addValidator('ticketcharset', { validateString: function (value) { return TEXT_ALLOWED.test(value || ''); }, messages: { en: MESSAGES.text } }); window.Parsley.addValidator('mobile10', { validateString: function (value, req, instance) { var m = (value || '').replace(/\D/g, ''); if (!instance.$element.prop('required') && m.length === 0) { return true; } if (instance.$element.prop('required') && m.length === 0) { return true; } return m.length === 10; }, messages: { en: MESSAGES.mobile } }); window.Parsley.addValidator('pincode6', { validateString: function (value, req, instance) { var p = (value || '').replace(/\D/g, ''); if (!instance.$element.prop('required') && p.length === 0) { return true; } if (instance.$element.prop('required') && p.length === 0) { return true; } return p.length === 6; }, messages: { en: MESSAGES.pincode } }); window.Parsley.addValidator('digitonly', { validateString: function (value) { if (!value || String(value).trim() === '') { return true; } return /^\d+$/.test(String(value).replace(/\D/g, '')); }, messages: { en: MESSAGES.digits } }); window.Parsley.addValidator('ticketdocname', { validateString: function (value) { if (!value || String(value).trim() === '') { return true; } return DOCNAME_ALLOWED.test(String(value)); }, messages: { en: MESSAGES.docname } }); window.Parsley.addValidator('ticketclaimurl', { validateString: function (value) { return isClaimUploadUrlString(value); }, messages: { en: MESSAGES.claimurl } }); window.Parsley.addValidator('claimfileext', { validateString: function (value, requirement, instance) { var el = instance.$element[0]; if (!el || el.type !== 'file') { return true; } if (!el.files || el.files.length === 0) { return true; } var n = el.files[0].name || ''; return /\.(pdf|jpe?g|png)$/i.test(n); }, messages: { en: MESSAGES.claimfile } }); window.Parsley.addValidator('ticketemail', { validateString: function (value) { if (!value || String(value).trim() === '') { return true; } return EMAIL_RE.test(String(value).trim()); }, messages: { en: MESSAGES.email } }); window.Parsley.addValidator('ticketpersonalemail', { validateString: function (value) { if (!value || String(value).trim() === '') { return true; } var v = String(value).trim(); var at = v.indexOf('@'); if (at < 1) { return false; } var domain = v.slice(at + 1); if (!domain || domain.indexOf('.') === -1) { return false; } return true; }, messages: { en: MESSAGES.email } }); window.Parsley.__ticketFormValidatorsRegistered = true; } function clearParsleyDataAttrs($el) { [ 'data-parsley-ticketcharset', 'data-parsley-mobile10', 'data-parsley-pincode6', 'data-parsley-digitonly', 'data-parsley-ticketemail', 'data-parsley-ticketpersonalemail', 'data-parsley-type', 'data-parsley-ticketdocname', 'data-parsley-ticketclaimurl', 'data-parsley-claimfileext', 'data-parsley-trigger' ].forEach(function (a) { $el.removeAttr(a); }); } function applyParsleyErrorTargets($form) { if (!$form || !$form.length) { return; } $form.find('input, textarea, select').each(function () { var el = this; if (el.type === 'hidden') { return; } var id = el.id; if (!id) { return; } if (isReadonly(el)) { return; } var cid = 'parsley-errors-' + id; var $el = $(el); if (!document.getElementById(cid)) { var $fg = $el.closest('.form-group'); if (!$fg.length) { $fg = $el.parent(); } if (!$fg.length) { $fg = $el.closest('.col-md-5, .col-md-7'); } if (!$fg.length) { $fg = $el.parent(); } $fg.append($('
')); } $el.attr('data-parsley-errors-container', '#' + cid); }); } function applyParsleyConstraints($form) { if (!$form || !$form.length) { return; } $form.find('input').each(function () { var el = this; if (el.type === 'hidden' || isReadonly(el)) { return; } var id = el.id; if (!id) { return; } var kind = getFieldKindFromElement(el); var $el = $(el); if (el.type === 'file') { if (kind !== 'claimfile') { return; } clearParsleyDataAttrs($el); $el.attr('data-parsley-claimfileext', 'true'); $el.attr('data-parsley-trigger', 'change'); return; } clearParsleyDataAttrs($el); if (kind === 'email') { $el.attr('data-parsley-ticketemail', 'true'); return; } if (kind === 'personalemail') { $el.attr('data-parsley-ticketpersonalemail', 'true'); return; } if (kind === 'mobile') { $el.attr('data-parsley-mobile10', 'true'); return; } if (kind === 'pincode') { $el.attr('data-parsley-pincode6', 'true'); return; } if (kind === 'digits') { $el.attr('data-parsley-digitonly', 'true'); return; } if (kind === 'urlfield') { $el.attr('data-parsley-ticketclaimurl', 'true'); $el.attr('data-parsley-trigger', 'blur'); return; } if (kind === 'docname') { $el.attr('data-parsley-ticketdocname', 'true'); $el.attr('data-parsley-trigger', 'blur'); return; } $el.attr('data-parsley-ticketcharset', 'true'); }); } function onFormInput(e) { var el = e.target; if (!el || (el.tagName !== 'INPUT' && el.tagName !== 'TEXTAREA')) { return; } revalidateParsleyField(el); } function revalidateParsleyField(el) { if (!el || !el.id || isReadonly(el)) { return; } if (!window.Parsley) { return; } var $el = $(el); try { $el.parsley().validate(); } catch (e) { /* ignore */ } } function validateAll(form) { var formEl = form || document.getElementById('ticket_form_data'); if (!formEl) { return true; } var $f = $(formEl); if (!$f.length) { return true; } if (typeof $f.parsley !== 'function') { return false; } var p = $f.parsley(); if (!p) { return true; } return p.validate(); } window.validateTicketFormInputs = validateAll; /** * Re-apply Parsley attrs after dynamic rows (e.g. claim file upload). Does not duplicate event handlers. */ window.refreshTicketFormValidationForForm = function (formSelector) { var $form = $(formSelector); if (!$form.length || !window.Parsley) { return; } registerParsleyTicketValidators(); applyParsleyErrorTargets($form); applyParsleyConstraints($form); $form.attr('novalidate', 'novalidate'); try { var inst = $form.parsley(); if (inst && typeof inst.refresh === 'function') { inst.refresh(); } } catch (e) { /* ignore */ } }; window.initTicketFormInputValidation = function (formSelector) { var $form = $(formSelector || '#ticket_form_data'); if (!$form.length) { return; } if (!window.Parsley) { return; } registerParsleyTicketValidators(); applyParsleyErrorTargets($form); applyParsleyConstraints($form); $form.attr('novalidate', 'novalidate'); $form.off('.ticketFormValidate'); $form.on('input.ticketFormValidate', 'input:not([type=hidden]), textarea', onFormInput); $form.on('blur.ticketFormValidate', 'input:not([type=hidden]), textarea', function () { revalidateParsleyField(this); }); $form.on('change.ticketFormValidate', 'input[type=file]', function () { revalidateParsleyField(this); }); $form.on('change.ticketFormValidate', 'select', function () { revalidateParsleyField(this); }); }; $(function () { ['#ticket_form_data', '#drive_file_upload_form', '#edit_url_form', '#ir_documents_form'].forEach(function (sel) { if ($(sel).length) { initTicketFormInputValidation(sel); } }); }); })(function () { return window.jQuery; });