/**
* 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 = $(`
`);
// 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);