/** * JVBase SEO Admin Interface * * Handles: * - Tab navigation * - Dynamic schema field loading * - Save/reset functionality * - Repeater field management */ (function($) { 'use strict'; const SEOAdmin = { config: window.jvbSeoConfig || {}, init() { this.bindTabs(); this.bindSchemaTypeChange(); this.bindSaveButtons(); this.bindResetButtons(); this.bindRepeaterFields(); this.bindImageSelectors(); }, /** * Tab navigation */ bindTabs() { $('.jvb-seo-tabs .tab-btn').on('click', function() { const tab = $(this).data('tab'); // Update active tab button $('.jvb-seo-tabs .tab-btn').removeClass('active'); $(this).addClass('active'); // Show corresponding content $('.tab-content').removeClass('active'); $(`.tab-content[data-tab="${tab}"]`).addClass('active'); }); }, /** * Dynamic schema field loading when type changes */ bindSchemaTypeChange() { $(document).on('change', '.schema-type-select', async function() { const $select = $(this); const type = $select.val(); const $fieldset = $select.closest('.jvb-seo-fieldset'); const $schemaFields = $fieldset.find('.schema-fields'); if (!type) { $schemaFields.hide().empty(); return; } // Fetch fields for this schema type try { const response = await fetch( `${SEOAdmin.config.restUrl}schema-fields/${type}`, { headers: { 'X-WP-Nonce': SEOAdmin.config.nonce } } ); const data = await response.json(); if (data.fields) { SEOAdmin.renderSchemaFields($schemaFields, data.fields); $schemaFields.show(); } } catch (error) { console.error('Failed to load schema fields:', error); } }); }, /** * Render schema field mappings */ renderSchemaFields($container, fields) { $container.empty(); Object.entries(fields).forEach(([key, config]) => { const $field = $(`
${config.description ? `${config.description}` : ''}
`); $container.append($field); }); }, /** * Save button handlers */ bindSaveButtons() { // Save site config $('#save-site-config').on('click', async function() { const $btn = $(this); $btn.prop('disabled', true).text('Saving...'); const data = SEOAdmin.collectSiteFormData(); try { const response = await fetch(`${SEOAdmin.config.restUrl}site`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': SEOAdmin.config.nonce }, body: JSON.stringify(data) }); const result = await response.json(); if (result.success) { SEOAdmin.showNotice('Settings saved successfully.', 'success'); } else { SEOAdmin.showNotice(result.message || 'Failed to save.', 'error'); } } catch (error) { SEOAdmin.showNotice('Network error. Please try again.', 'error'); } finally { $btn.prop('disabled', false).text('Save Site Settings'); } }); // Save content type config $(document).on('click', '.save-content-type', async function() { const $btn = $(this); const $container = $btn.closest('.jvb-seo-content-type'); const type = $container.data('type'); const objectType = $container.data('object-type'); $btn.prop('disabled', true).text('Saving...'); const data = SEOAdmin.collectContentTypeFormData($container); try { const response = await fetch( `${SEOAdmin.config.restUrl}content/${objectType}/${type}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': SEOAdmin.config.nonce }, body: JSON.stringify(data) } ); const result = await response.json(); if (result.success) { SEOAdmin.showNotice('Settings saved.', 'success'); } else { SEOAdmin.showNotice(result.message || 'Failed to save.', 'error'); } } catch (error) { SEOAdmin.showNotice('Network error.', 'error'); } finally { $btn.prop('disabled', false).text('Save'); } }); }, /** * Reset button handlers */ bindResetButtons() { $(document).on('click', '.reset-to-defaults', async function() { if (!confirm('Reset to default settings? This cannot be undone.')) { return; } const $btn = $(this); const $container = $btn.closest('.jvb-seo-content-type'); const type = $container.data('type'); const objectType = $container.data('object-type'); $btn.prop('disabled', true).text('Resetting...'); try { const response = await fetch( `${SEOAdmin.config.restUrl}content/${objectType}/${type}/reset`, { method: 'POST', headers: { 'X-WP-Nonce': SEOAdmin.config.nonce } } ); const result = await response.json(); if (result.success) { // Reload page to show defaults location.reload(); } else { SEOAdmin.showNotice(result.message || 'Failed to reset.', 'error'); } } catch (error) { SEOAdmin.showNotice('Network error.', 'error'); } finally { $btn.prop('disabled', false).text('Reset to Defaults'); } }); }, /** * Repeater field management */ bindRepeaterFields() { // Add row $(document).on('click', '.repeater-field .add-row', function() { const $repeater = $(this).closest('.repeater-field'); const fieldName = $repeater.data('field'); const $row = $(`
`); $repeater.find('.add-row').before($row); }); // Remove row $(document).on('click', '.repeater-field .remove-row', function() { $(this).closest('.repeater-row').remove(); }); }, /** * WordPress Media Library image selector */ bindImageSelectors() { $(document).on('click', '.select-image', function(e) { e.preventDefault(); const $button = $(this); const $field = $button.siblings('input[type="hidden"]'); const $preview = $button.siblings('.image-preview'); const frame = wp.media({ title: 'Select Image', button: { text: 'Use Image' }, multiple: false }); frame.on('select', function() { const attachment = frame.state().get('selection').first().toJSON(); $field.val(attachment.id); $preview.html(``); }); frame.open(); }); }, /** * Collect site form data */ collectSiteFormData() { const $form = $('[data-tab="site"]'); const data = {}; // Single fields $form.find('input[name], textarea[name], select[name]').each(function() { const name = $(this).attr('name'); if (!name.endsWith('[]')) { data[name] = $(this).val(); } }); // Repeater fields (sameAs) $form.find('[name="organization_sameas[]"]').each(function() { if (!data.organization_sameas) { data.organization_sameas = []; } const val = $(this).val(); if (val) { data.organization_sameas.push(val); } }); return data; }, /** * Collect content type form data */ collectContentTypeFormData($container) { const data = { meta_title: $container.find('[name="meta_title"]').val(), meta_description: $container.find('[name="meta_description"]').val(), schema_type: $container.find('[name="schema_type"]').val(), schema_fields: {} }; // Collect schema field mappings $container.find('[name^="schema_fields"]').each(function() { const match = $(this).attr('name').match(/schema_fields\[(\w+)\]/); if (match) { data.schema_fields[match[1]] = $(this).val(); } }); return data; }, /** * Show notice message */ showNotice(message, type = 'info') { const $notice = $(`

${message}

`); // Remove existing notices $('.jvb-seo-admin .notice').remove(); // Add new notice $('.jvb-seo-admin').prepend($notice); // Auto-dismiss after 5 seconds setTimeout(() => $notice.fadeOut(), 5000); } }; // Initialize when DOM is ready $(document).ready(() => SEOAdmin.init()); })(jQuery);