474 lines
15 KiB
JavaScript
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;
|
|
});
|