nhance/public/assets/js/pages/ticket_form_input_validation.js

474 lines
15 KiB
JavaScript

/**
* 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($('<div class="parsley-errors-target" id="' + cid + '"></div>'));
}
$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;
});