upskill-event-manager/assets/js/hvac-announcements-admin.js
ben 2a06bb1f15 fix: resolve announcements modal z-index stacking issue (v2.1.5)
Changes:
- Fix z-index conflict where announcement modal (999999) was higher than WordPress media modals (160000)
- Reduce announcement modal z-index to 100000 to allow WordPress media library to stack on top
- Remove duplicate TinyMCE initialization that was unnecessary
- Add custom "Add Media" button that renders when modal opens (prevents hidden modal issues)
- Improve page detection with multi-layered approach (URL path, template, slug, queried object)
- Move script loading to footer for better WordPress editor compatibility

Technical Details:
- WordPress core media modals use z-index 160000-160010
- Custom plugin modals should use 100000-159000 range to avoid conflicts
- wp_editor() with media_buttons => true in hidden modals causes auto-open issues
- Solution: media_buttons => false + custom button added via JavaScript when modal opens

Testing:
- Verified with MCP Playwright browser automation
- Media modal now properly appears above announcement modal
- All form functionality preserved
- Screenshot verification shows correct stacking order

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 19:23:02 -04:00

567 lines
No EOL
18 KiB
JavaScript

/**
* HVAC Announcements Admin JavaScript
*
* @package HVAC_Community_Events
*/
jQuery(document).ready(function($) {
'use strict';
// State variables
let currentPage = 1;
let totalPages = 1;
let currentStatus = 'any';
let searchTerm = '';
let editorInstance = null;
// Initialize
init();
/**
* Initialize the announcements interface
*/
function init() {
loadAnnouncements();
loadCategories();
initializeEventHandlers();
// NOTE: Editor initialization removed - wp_editor() PHP function handles this
// The duplicate JavaScript initialization was causing the media modal to auto-open
}
/**
* Initialize event handlers
*/
function initializeEventHandlers() {
// Add announcement button - FIXED: Use correct class selector to match HTML template
$('.hvac-add-announcement').on('click', function() {
openModal();
});
// Modal close buttons
$('.modal-close, .modal-cancel').on('click', function() {
closeModal();
});
// Form submission
$('#announcement-form').on('submit', function(e) {
e.preventDefault();
saveAnnouncement();
});
// Status filter
$('#status-filter').on('change', function() {
currentStatus = $(this).val();
currentPage = 1;
loadAnnouncements();
});
// Search
$('#search-btn').on('click', function() {
searchTerm = $('#announcement-search').val();
currentPage = 1;
loadAnnouncements();
});
$('#announcement-search').on('keypress', function(e) {
if (e.which === 13) {
searchTerm = $(this).val();
currentPage = 1;
loadAnnouncements();
}
});
// Pagination
$('#prev-page').on('click', function() {
if (currentPage > 1) {
currentPage--;
loadAnnouncements();
}
});
$('#next-page').on('click', function() {
if (currentPage < totalPages) {
currentPage++;
loadAnnouncements();
}
});
// Edit/Delete actions (delegated)
$(document).on('click', '.edit-announcement', function() {
const id = $(this).data('id');
editAnnouncement(id);
});
$(document).on('click', '.delete-announcement', function() {
const id = $(this).data('id');
if (confirm(hvac_announcements.strings.confirm_delete)) {
deleteAnnouncement(id);
}
});
// Featured image selection
$('#select-featured-image').on('click', function(e) {
e.preventDefault();
selectFeaturedImage();
});
$('#remove-featured-image').on('click', function(e) {
e.preventDefault();
removeFeaturedImage();
});
}
/**
* Load announcements via AJAX
*/
function loadAnnouncements() {
const data = {
action: 'hvac_get_announcements',
nonce: hvac_announcements.nonce,
page: currentPage,
per_page: 20,
status: currentStatus,
search: searchTerm
};
$.post(hvac_announcements.ajax_url, data, function(response) {
if (response.success) {
displayAnnouncements(response.data.announcements);
updatePagination(response.data.current_page, response.data.pages);
} else {
showError(response.data || hvac_announcements.strings.error_loading);
}
});
}
/**
* Display announcements in table
*/
function displayAnnouncements(announcements) {
const tbody = $('#announcements-list');
tbody.empty();
if (announcements.length === 0) {
tbody.append('<tr><td colspan="6" class="no-items">No announcements found</td></tr>');
return;
}
announcements.forEach(function(announcement) {
const row = $('<tr>');
// Title
row.append('<td class="column-title"><strong>' + escapeHtml(announcement.title) + '</strong></td>');
// Status
const statusClass = 'status-' + announcement.status;
row.append('<td class="column-status"><span class="' + statusClass + '">' + announcement.status + '</span></td>');
// Categories
const categories = announcement.categories.join(', ') || '-';
row.append('<td class="column-categories">' + escapeHtml(categories) + '</td>');
// Author
row.append('<td class="column-author">' + escapeHtml(announcement.author) + '</td>');
// Date
row.append('<td class="column-date">' + announcement.date + '</td>');
// Actions
let actions = '<td class="column-actions">';
if (announcement.can_edit) {
actions += '<button class="button button-small edit-announcement" data-id="' + announcement.id + '">Edit</button> ';
}
if (announcement.can_delete) {
actions += '<button class="button button-small delete-announcement" data-id="' + announcement.id + '">Delete</button>';
}
actions += '</td>';
row.append(actions);
tbody.append(row);
});
}
/**
* Update pagination controls
*/
function updatePagination(current, total) {
currentPage = current;
totalPages = total;
$('#current-page').text(current);
$('#total-pages').text(total);
$('#prev-page').prop('disabled', current <= 1);
$('#next-page').prop('disabled', current >= total);
}
/**
* Load categories for the form
*/
function loadCategories() {
$.post(hvac_announcements.ajax_url, {
action: 'hvac_get_announcement_categories',
nonce: hvac_announcements.nonce
}, function(response) {
if (response.success) {
displayCategories(response.data);
}
});
}
/**
* Display categories as checkboxes
*/
function displayCategories(categories) {
const container = $('#categories-container');
container.empty();
if (categories.length === 0) {
container.append('<p class="no-categories">No categories available</p>');
return;
}
categories.forEach(function(category) {
const checkbox = $('<label class="category-checkbox">');
checkbox.append('<input type="checkbox" name="categories[]" value="' + category.id + '">');
checkbox.append(' ' + escapeHtml(category.name));
container.append(checkbox);
});
}
/**
* Open the modal for adding/editing
*/
function openModal(announcementId) {
const $modal = $('#announcement-modal');
$modal.addClass('active');
$('body').addClass('modal-open');
// Add custom "Add Media" button to TinyMCE editor toolbar
// This is needed because media_buttons => false in wp_editor() to prevent auto-open
addMediaButtonToEditor();
if (announcementId) {
$('#modal-title').text('Edit Announcement');
loadAnnouncementForEdit(announcementId);
} else {
$('#modal-title').text('Add New Announcement');
resetForm();
}
}
/**
* Add custom "Add Media" button to TinyMCE editor
* Required because wp_editor has media_buttons => false to prevent auto-open in hidden modal
*/
function addMediaButtonToEditor() {
// Check if button already exists
if ($('#wp-announcement-content-media-buttons').length > 0) {
return;
}
// Create media button container
const $mediaButton = $('<div id="wp-announcement-content-media-buttons" class="wp-media-buttons"></div>');
const $addMediaBtn = $('<button type="button" id="insert-media-button" class="button insert-media add_media" data-editor="announcement-content"></button>');
$addMediaBtn.html('<span class="wp-media-buttons-icon"></span> Add Media');
// Add click handler
$addMediaBtn.on('click', function(e) {
e.preventDefault();
openEditorMediaUploader();
});
$mediaButton.append($addMediaBtn);
// Insert button before editor wrapper
$('#wp-announcement-content-wrap').before($mediaButton);
}
/**
* Open media uploader for the announcement content editor
*/
function openEditorMediaUploader() {
if (typeof wp.media === 'undefined') {
return;
}
const editorMediaUploader = wp.media({
title: 'Insert Media',
button: {
text: 'Insert into post'
},
multiple: true
});
editorMediaUploader.on('select', function() {
const selection = editorMediaUploader.state().get('selection');
const editor = tinymce.get('announcement-content');
selection.forEach(function(attachment) {
attachment = attachment.toJSON();
let html = '';
if (attachment.type === 'image') {
html = '<img src="' + attachment.url + '" alt="' + (attachment.alt || '') + '" />';
} else {
html = '<a href="' + attachment.url + '">' + attachment.title + '</a>';
}
if (editor) {
editor.insertContent(html);
}
});
});
editorMediaUploader.open();
}
/**
* Close the modal
*/
function closeModal() {
const $modal = $('#announcement-modal');
$modal.removeClass('active');
$('body').removeClass('modal-open');
resetForm();
}
/**
* Reset the form
*/
function resetForm() {
$('#announcement-form')[0].reset();
$('#announcement-id').val('');
// Reset editor using TinyMCE native API
if (typeof tinymce !== 'undefined') {
const editor = tinymce.get('announcement-content');
if (editor) {
editor.setContent('');
}
}
// Reset featured image
removeFeaturedImage();
// Uncheck all categories
$('#categories-container input[type="checkbox"]').prop('checked', false);
}
/**
* Load announcement for editing
*/
function loadAnnouncementForEdit(id) {
$.post(hvac_announcements.ajax_url, {
action: 'hvac_get_announcement',
nonce: hvac_announcements.nonce,
id: id
}, function(response) {
if (response.success) {
populateForm(response.data);
} else {
showError(response.data || 'Failed to load announcement');
closeModal();
}
});
}
/**
* Populate form with announcement data
*/
function populateForm(announcement) {
$('#announcement-id').val(announcement.id);
$('#announcement-title').val(announcement.title);
$('#announcement-excerpt').val(announcement.excerpt);
$('#announcement-status').val(announcement.status);
$('#announcement-tags').val(announcement.tags);
// Set content in editor using TinyMCE native API
if (typeof tinymce !== 'undefined') {
const editor = tinymce.get('announcement-content');
if (editor) {
editor.setContent(announcement.content || '');
}
}
// Set publish date
if (announcement.date) {
const date = new Date(announcement.date);
const localDate = date.toISOString().slice(0, 16);
$('#announcement-date').val(localDate);
}
// Set categories
if (announcement.categories && announcement.categories.length > 0) {
announcement.categories.forEach(function(catId) {
$('#categories-container input[value="' + catId + '"]').prop('checked', true);
});
}
// Set featured image
if (announcement.featured_image_id) {
$('#featured-image-id').val(announcement.featured_image_id);
if (announcement.featured_image_url) {
$('#featured-image-preview').html('<img src="' + announcement.featured_image_url + '" />');
$('#remove-featured-image').show();
}
}
}
/**
* Save announcement (create or update)
*/
function saveAnnouncement() {
// Get editor content using WordPress editor API or TinyMCE native API
let content = '';
if (typeof wp !== 'undefined' && wp.editor && wp.editor.getContent) {
// Use WordPress API if available (WordPress 4.8+)
content = wp.editor.getContent('announcement-content');
} else if (typeof tinymce !== 'undefined') {
// Fallback to TinyMCE native API
const editor = tinymce.get('announcement-content');
if (editor) {
content = editor.getContent();
}
}
// Gather form data
const formData = {
action: $('#announcement-id').val() ? 'hvac_update_announcement' : 'hvac_create_announcement',
nonce: hvac_announcements.nonce,
id: $('#announcement-id').val(),
title: $('#announcement-title').val(),
content: content,
excerpt: $('#announcement-excerpt').val(),
status: $('#announcement-status').val(),
publish_date: $('#announcement-date').val(),
tags: $('#announcement-tags').val(),
categories: [],
featured_image_id: $('#featured-image-id').val()
};
// Get selected categories
$('#categories-container input:checked').each(function() {
formData.categories.push($(this).val());
});
// Send AJAX request
$.post(hvac_announcements.ajax_url, formData, function(response) {
if (response.success) {
showSuccess(response.data.message);
closeModal();
loadAnnouncements();
} else {
showError(response.data || hvac_announcements.strings.error_saving);
}
});
}
/**
* Edit announcement
*/
function editAnnouncement(id) {
openModal(id);
}
/**
* Delete announcement
*/
function deleteAnnouncement(id) {
$.post(hvac_announcements.ajax_url, {
action: 'hvac_delete_announcement',
nonce: hvac_announcements.nonce,
id: id
}, function(response) {
if (response.success) {
showSuccess(response.data.message || hvac_announcements.strings.success_deleted);
loadAnnouncements();
} else {
showError(response.data || 'Failed to delete announcement');
}
});
}
/**
* Select featured image using WordPress media uploader
*/
function selectFeaturedImage() {
if (typeof wp.media === 'undefined') {
return;
}
const mediaUploader = wp.media({
title: 'Select Featured Image',
button: {
text: 'Use this image'
},
multiple: false
});
mediaUploader.on('select', function() {
const attachment = mediaUploader.state().get('selection').first().toJSON();
$('#featured-image-id').val(attachment.id);
let imageUrl = attachment.url;
if (attachment.sizes && attachment.sizes.medium) {
imageUrl = attachment.sizes.medium.url;
}
$('#featured-image-preview').html('<img src="' + imageUrl + '" />');
$('#remove-featured-image').show();
});
mediaUploader.open();
}
/**
* Remove featured image
*/
function removeFeaturedImage() {
$('#featured-image-id').val('');
$('#featured-image-preview').empty();
$('#remove-featured-image').hide();
}
/**
* Show success message
*/
function showSuccess(message) {
const notice = $('<div class="notice notice-success is-dismissible"><p>' + message + '</p></div>');
$('.hvac-announcements-wrapper').prepend(notice);
setTimeout(function() {
notice.fadeOut(function() {
notice.remove();
});
}, 3000);
}
/**
* Show error message
*/
function showError(message) {
const notice = $('<div class="notice notice-error is-dismissible"><p>' + message + '</p></div>');
$('.hvac-announcements-wrapper').prepend(notice);
setTimeout(function() {
notice.fadeOut(function() {
notice.remove();
});
}, 5000);
}
/**
* Escape HTML
*/
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}
});