// Ajax Modal Load Script (deduped + locked) function hashKey(str) { let h = 0; for (let i = 0; i < str.length; i++) { h = ((h << 5) - h + str.charCodeAt(i)) | 0; } return Math.abs(h).toString(36); } $(document).on('click', '.ajax-modal', function (e) { e.preventDefault(); const $trigger = $(this); // prevent spam clicks on same trigger if ($trigger.data('ajaxModalLoading')) { return; } $trigger .data('ajaxModalLoading', true) .prop('disabled', true) .addClass('disabled'); // Prefer data-modal-url, fallback to href const modalUrl = $trigger.data('modal-url') || $trigger.attr('href') || '#'; const modalSize = $trigger.data('modal-size') || 'md'; if (!modalUrl || modalUrl === '#') { console.warn('ajax-modal: No modal URL found on trigger:', this); $trigger .data('ajaxModalLoading', false) .prop('disabled', false) .removeClass('disabled'); return; } // stable IDs based on URL (prevents duplicates) const key = hashKey(String(modalUrl)); const modalId = 'ajaxModal_' + key; const spinnerId = 'modal-loading-spinner-' + key; // if modal already exists, just show it const $existing = $('#' + modalId); if ($existing.length) { $existing.modal('show'); $trigger .data('ajaxModalLoading', false) .prop('disabled', false) .removeClass('disabled'); return; } // Show loading spinner while fetching content (deduped) $('#' + spinnerId).remove(); $('.content-wrapper').append(`