- Add featured image field to main event creation form with WordPress media uploader - Implement featured image upload in organizer and venue creation modals - Update AJAX handlers to process and validate featured image attachments - Add comprehensive media upload UI with preview and removal functionality - Include proper permission validation for administrator, trainer, and master trainer roles - Create authoritative documentation for complete event creation page functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
405 lines
No EOL
16 KiB
JavaScript
405 lines
No EOL
16 KiB
JavaScript
/**
|
||
* HVAC Modal Forms
|
||
*
|
||
* Handles modal forms for creating new organizers, categories, and venues
|
||
* with role-based permissions and AJAX submission.
|
||
*/
|
||
|
||
(function($) {
|
||
'use strict';
|
||
|
||
class HVACModalForms {
|
||
constructor() {
|
||
this.init();
|
||
}
|
||
|
||
init() {
|
||
this.bindEvents();
|
||
this.createModalContainer();
|
||
}
|
||
|
||
bindEvents() {
|
||
// Listen for create new modal trigger
|
||
$(document).on('hvac:create-new-modal', (e, data) => {
|
||
this.showCreateModal(data.type, data.callback);
|
||
});
|
||
|
||
// Modal close events
|
||
$(document).on('click', '.hvac-modal-overlay, .hvac-modal-close', (e) => {
|
||
e.preventDefault();
|
||
this.closeModal();
|
||
});
|
||
|
||
// Prevent modal close when clicking inside modal content
|
||
$(document).on('click', '.hvac-modal-content', (e) => {
|
||
e.stopPropagation();
|
||
});
|
||
|
||
// Form submission
|
||
$(document).on('submit', '.hvac-modal-form', (e) => {
|
||
e.preventDefault();
|
||
this.handleFormSubmission(e.target);
|
||
});
|
||
|
||
// Escape key to close modal
|
||
$(document).on('keydown', (e) => {
|
||
if (e.keyCode === 27) { // ESC key
|
||
this.closeModal();
|
||
}
|
||
});
|
||
|
||
// Media upload handlers
|
||
$(document).on('click', '.hvac-modal .select-image-btn', (e) => {
|
||
e.preventDefault();
|
||
this.openMediaUploader(e.target);
|
||
});
|
||
|
||
$(document).on('click', '.hvac-modal .remove-image', (e) => {
|
||
e.preventDefault();
|
||
this.removeImage(e.target);
|
||
});
|
||
}
|
||
|
||
createModalContainer() {
|
||
if ($('#hvac-modal-container').length) {
|
||
return;
|
||
}
|
||
|
||
const modalHtml = `
|
||
<div id="hvac-modal-container" class="hvac-modal-overlay" style="display: none;">
|
||
<div class="hvac-modal-content">
|
||
<div class="hvac-modal-header">
|
||
<h3 class="hvac-modal-title"></h3>
|
||
<button type="button" class="hvac-modal-close">×</button>
|
||
</div>
|
||
<div class="hvac-modal-body">
|
||
<!-- Form content will be inserted here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
$('body').append(modalHtml);
|
||
}
|
||
|
||
showCreateModal(type, callback) {
|
||
this.currentCallback = callback;
|
||
|
||
const config = this.getModalConfig(type);
|
||
if (!config) {
|
||
console.error(`Unknown modal type: ${type}`);
|
||
return;
|
||
}
|
||
|
||
// Set modal title
|
||
$('.hvac-modal-title').text(config.title);
|
||
|
||
// Generate form HTML
|
||
const formHtml = this.generateFormHtml(type, config);
|
||
$('.hvac-modal-body').html(formHtml);
|
||
|
||
// Show modal
|
||
$('#hvac-modal-container').fadeIn(300);
|
||
|
||
// Focus first input
|
||
setTimeout(() => {
|
||
$('.hvac-modal-form input:first').focus();
|
||
}, 350);
|
||
}
|
||
|
||
getModalConfig(type) {
|
||
const configs = {
|
||
organizer: {
|
||
title: 'Add New Organizer',
|
||
fields: [
|
||
{ name: 'organizer_name', label: 'Organizer Name', type: 'text', required: true },
|
||
{ name: 'organizer_email', label: 'Email', type: 'email', required: false },
|
||
{ name: 'organizer_website', label: 'Website', type: 'url', required: false },
|
||
{ name: 'organizer_phone', label: 'Phone', type: 'tel', required: false },
|
||
{ name: 'organizer_featured_image', label: 'Featured Image', type: 'media', required: false }
|
||
],
|
||
action: 'hvac_create_organizer'
|
||
},
|
||
category: {
|
||
title: 'Add New Category',
|
||
fields: [
|
||
{ name: 'category_name', label: 'Category Name', type: 'text', required: true },
|
||
{ name: 'category_description', label: 'Description', type: 'textarea', required: false }
|
||
],
|
||
action: 'hvac_create_category',
|
||
permission_check: true
|
||
},
|
||
venue: {
|
||
title: 'Add New Venue',
|
||
fields: [
|
||
{ name: 'venue_name', label: 'Venue Name', type: 'text', required: true },
|
||
{ name: 'venue_address', label: 'Address', type: 'text', required: false },
|
||
{ name: 'venue_city', label: 'City', type: 'text', required: false },
|
||
{ name: 'venue_state', label: 'State/Province', type: 'text', required: false },
|
||
{ name: 'venue_zip', label: 'Zip/Postal Code', type: 'text', required: false },
|
||
{ name: 'venue_country', label: 'Country', type: 'text', required: false },
|
||
{ name: 'venue_website', label: 'Website', type: 'url', required: false },
|
||
{ name: 'venue_phone', label: 'Phone', type: 'tel', required: false },
|
||
{ name: 'venue_featured_image', label: 'Featured Image', type: 'media', required: false }
|
||
],
|
||
action: 'hvac_create_venue'
|
||
}
|
||
};
|
||
|
||
return configs[type] || null;
|
||
}
|
||
|
||
generateFormHtml(type, config) {
|
||
// Check for category permission
|
||
if (config.permission_check && !hvacModalForms.canCreateCategories) {
|
||
return `
|
||
<div class="hvac-permission-error">
|
||
<p><strong>Permission Denied</strong></p>
|
||
<p>You don't have permission to create new categories. Please contact a master trainer for assistance.</p>
|
||
<div class="hvac-modal-actions">
|
||
<button type="button" class="hvac-btn hvac-btn-secondary hvac-modal-close">Close</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
let formHtml = `
|
||
<form class="hvac-modal-form" data-action="${config.action}">
|
||
<div class="hvac-form-fields">
|
||
`;
|
||
|
||
config.fields.forEach(field => {
|
||
formHtml += this.generateFieldHtml(field);
|
||
});
|
||
|
||
formHtml += `
|
||
</div>
|
||
<div class="hvac-modal-actions">
|
||
<button type="button" class="hvac-btn hvac-btn-secondary hvac-modal-close">Cancel</button>
|
||
<button type="submit" class="hvac-btn hvac-btn-primary">Create ${this.capitalizeFirst(type)}</button>
|
||
</div>
|
||
</form>
|
||
`;
|
||
|
||
return formHtml;
|
||
}
|
||
|
||
generateFieldHtml(field) {
|
||
const required = field.required ? 'required' : '';
|
||
const requiredMark = field.required ? '<span class="required">*</span>' : '';
|
||
|
||
if (field.type === 'textarea') {
|
||
return `
|
||
<div class="hvac-form-field">
|
||
<label for="${field.name}">${field.label}${requiredMark}</label>
|
||
<textarea id="${field.name}" name="${field.name}" ${required} rows="3"></textarea>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
if (field.type === 'media') {
|
||
return `
|
||
<div class="hvac-form-field hvac-media-field" data-field-name="${field.name}">
|
||
<label>${field.label}${requiredMark}</label>
|
||
<div class="media-upload-container">
|
||
<div class="image-preview-container" style="margin-bottom: 10px;">
|
||
<div class="image-preview" style="display: none; position: relative; max-width: 200px;">
|
||
<img src="" alt="Preview" style="max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 4px;">
|
||
<button type="button" class="remove-image" style="position: absolute; top: 5px; right: 5px; background: #d63638; color: white; border: none; border-radius: 50%; width: 20px; height: 20px; cursor: pointer; font-size: 12px;">×</button>
|
||
</div>
|
||
</div>
|
||
<div class="upload-controls">
|
||
<button type="button" class="select-image-btn hvac-btn hvac-btn-secondary">
|
||
<span class="dashicons dashicons-format-image" style="vertical-align: middle; margin-right: 5px;"></span>
|
||
Select Image
|
||
</button>
|
||
<input type="hidden" name="${field.name}" class="image-id-input" value="">
|
||
<input type="hidden" name="${field.name}_url" class="image-url-input" value="">
|
||
</div>
|
||
<p class="description" style="margin-top: 5px; font-size: 12px; color: #666;">
|
||
Recommended: 300x300 pixels or larger
|
||
</p>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
return `
|
||
<div class="hvac-form-field">
|
||
<label for="${field.name}">${field.label}${requiredMark}</label>
|
||
<input type="${field.type}" id="${field.name}" name="${field.name}" ${required}>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
async handleFormSubmission(form) {
|
||
const $form = $(form);
|
||
const $submitBtn = $form.find('button[type="submit"]');
|
||
const action = $form.data('action');
|
||
|
||
// Disable submit button and show loading
|
||
$submitBtn.prop('disabled', true).text('Creating...');
|
||
|
||
try {
|
||
const formData = new FormData(form);
|
||
formData.append('action', action);
|
||
formData.append('nonce', hvacModalForms.nonce);
|
||
|
||
const response = await fetch(hvacModalForms.ajaxUrl, {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
|
||
if (!result.success) {
|
||
throw new Error(result.data || 'Request failed');
|
||
}
|
||
|
||
// Success - call callback with new item
|
||
if (this.currentCallback) {
|
||
this.currentCallback(result.data);
|
||
}
|
||
|
||
this.closeModal();
|
||
this.showSuccessMessage(`Successfully created ${result.data.title}`);
|
||
|
||
} catch (error) {
|
||
console.error('Form submission error:', error);
|
||
this.showErrorMessage(error.message || 'Failed to create item');
|
||
} finally {
|
||
// Re-enable submit button
|
||
$submitBtn.prop('disabled', false).text($submitBtn.text().replace('Creating...', 'Create'));
|
||
}
|
||
}
|
||
|
||
closeModal() {
|
||
$('#hvac-modal-container').fadeOut(300);
|
||
this.currentCallback = null;
|
||
}
|
||
|
||
showSuccessMessage(message) {
|
||
// Create temporary success notification
|
||
const $notification = $(`
|
||
<div class="hvac-notification hvac-success">
|
||
<span class="dashicons dashicons-yes-alt"></span>
|
||
${this.escapeHtml(message)}
|
||
</div>
|
||
`);
|
||
|
||
$('body').append($notification);
|
||
|
||
setTimeout(() => {
|
||
$notification.addClass('show');
|
||
}, 100);
|
||
|
||
setTimeout(() => {
|
||
$notification.removeClass('show');
|
||
setTimeout(() => $notification.remove(), 300);
|
||
}, 3000);
|
||
}
|
||
|
||
showErrorMessage(message) {
|
||
// Create temporary error notification
|
||
const $notification = $(`
|
||
<div class="hvac-notification hvac-error">
|
||
<span class="dashicons dashicons-warning"></span>
|
||
${this.escapeHtml(message)}
|
||
</div>
|
||
`);
|
||
|
||
$('body').append($notification);
|
||
|
||
setTimeout(() => {
|
||
$notification.addClass('show');
|
||
}, 100);
|
||
|
||
setTimeout(() => {
|
||
$notification.removeClass('show');
|
||
setTimeout(() => $notification.remove(), 300);
|
||
}, 5000);
|
||
}
|
||
|
||
openMediaUploader(button) {
|
||
const $button = $(button);
|
||
const $fieldContainer = $button.closest('.hvac-media-field');
|
||
const $imagePreview = $fieldContainer.find('.image-preview');
|
||
const $previewImg = $fieldContainer.find('.image-preview img');
|
||
const $imageIdInput = $fieldContainer.find('.image-id-input');
|
||
const $imageUrlInput = $fieldContainer.find('.image-url-input');
|
||
|
||
// Create WordPress media frame
|
||
const mediaUploader = wp.media({
|
||
title: 'Select Image',
|
||
button: {
|
||
text: 'Select Image'
|
||
},
|
||
multiple: false,
|
||
library: {
|
||
type: 'image'
|
||
}
|
||
});
|
||
|
||
// When an image is selected
|
||
mediaUploader.on('select', () => {
|
||
const attachment = mediaUploader.state().get('selection').first().toJSON();
|
||
|
||
// Update hidden inputs
|
||
$imageIdInput.val(attachment.id);
|
||
$imageUrlInput.val(attachment.url);
|
||
|
||
// Update preview
|
||
$previewImg.attr('src', attachment.url);
|
||
$previewImg.attr('alt', attachment.alt || attachment.title || 'Selected image');
|
||
$imagePreview.show();
|
||
|
||
// Update button text
|
||
$button.html('<span class="dashicons dashicons-format-image" style="vertical-align: middle; margin-right: 5px;"></span>Change Image');
|
||
});
|
||
|
||
// Open the media modal
|
||
mediaUploader.open();
|
||
}
|
||
|
||
removeImage(button) {
|
||
const $button = $(button);
|
||
const $fieldContainer = $button.closest('.hvac-media-field');
|
||
const $imagePreview = $fieldContainer.find('.image-preview');
|
||
const $previewImg = $fieldContainer.find('.image-preview img');
|
||
const $imageIdInput = $fieldContainer.find('.image-id-input');
|
||
const $imageUrlInput = $fieldContainer.find('.image-url-input');
|
||
const $selectBtn = $fieldContainer.find('.select-image-btn');
|
||
|
||
// Clear inputs
|
||
$imageIdInput.val('');
|
||
$imageUrlInput.val('');
|
||
|
||
// Hide preview
|
||
$imagePreview.hide();
|
||
$previewImg.attr('src', '');
|
||
|
||
// Reset button text
|
||
$selectBtn.html('<span class="dashicons dashicons-format-image" style="vertical-align: middle; margin-right: 5px;"></span>Select Image');
|
||
}
|
||
|
||
capitalizeFirst(str) {
|
||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||
}
|
||
|
||
escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
}
|
||
|
||
// Initialize modal forms when document is ready
|
||
$(document).ready(function() {
|
||
new HVACModalForms();
|
||
});
|
||
|
||
})(jQuery); |