314 lines
No EOL
13 KiB
JavaScript
314 lines
No EOL
13 KiB
JavaScript
jQuery(document).ready(function($) {
|
|
console.log('Assessment Builder Script Loaded'); // Debugging
|
|
|
|
// --- Cache DOM Elements ---
|
|
const $libraryList = $('#library-questions-list');
|
|
const $currentList = $('#current-assessment-questions');
|
|
const $assessmentTitleInput = $('#assessment-title');
|
|
const $totalCostSpan = $('#assessment-total-cost');
|
|
const $saveButton = $('#save-assessment-button');
|
|
const $saveSpinner = $saveButton.siblings('.spinner');
|
|
const $saveStatus = $('#save-status');
|
|
|
|
// --- State ---
|
|
let currentAssessmentQuestions = []; // Array to hold IDs of questions in the right pane
|
|
let currentAssessmentId = null; // Store the ID if editing an existing assessment (TODO: Load this if editing)
|
|
|
|
// --- Functions ---
|
|
|
|
/**
|
|
* Updates the total cost display based on the questions currently in the assessment list.
|
|
*/
|
|
function updateTotalCost() {
|
|
let totalCost = 0;
|
|
$currentList.find('.selected-question-item').each(function() {
|
|
totalCost += parseInt($(this).data('cost') || 0);
|
|
});
|
|
$totalCostSpan.text(totalCost);
|
|
}
|
|
|
|
/**
|
|
* Updates the internal `currentAssessmentQuestions` array based on the current DOM order.
|
|
*/
|
|
function updateStateFromDOM() {
|
|
currentAssessmentQuestions = $currentList.find('.selected-question-item').map(function() {
|
|
return $(this).data('question-id');
|
|
}).get(); // .get() converts jQuery object to array
|
|
console.log('Updated State:', currentAssessmentQuestions); // Debugging
|
|
}
|
|
|
|
|
|
/**
|
|
* Fetches questions for the library pane via AJAX.
|
|
* TODO: Add parameters for search, filters, pagination.
|
|
*/
|
|
function fetchLibraryQuestions(page = 1, search = '') {
|
|
console.log('Fetching library questions...');
|
|
$libraryList.html('<p>' + quiztechBuilderData.loading_questions + '</p>'); // Show loading indicator
|
|
|
|
$.post(quiztechBuilderData.ajax_url, {
|
|
action: 'quiztech_fetch_library_questions',
|
|
// nonce: quiztechBuilderData.fetch_nonce, // TODO: Implement nonce verification in PHP
|
|
page: page,
|
|
search: search
|
|
})
|
|
.done(function(response) {
|
|
if (response.success && response.data.questions) {
|
|
renderLibraryQuestions(response.data.questions);
|
|
// TODO: Render pagination controls using response.data.pagination
|
|
} else {
|
|
$libraryList.html('<p class="error">' + (response.data.message || quiztechBuilderData.error_generic) + '</p>');
|
|
}
|
|
})
|
|
.fail(function() {
|
|
$libraryList.html('<p class="error">' + quiztechBuilderData.error_generic + '</p>');
|
|
})
|
|
.always(function() {
|
|
// Hide loading indicator if needed
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Renders the fetched questions into the library list container.
|
|
* @param {Array} questions Array of question objects {id, title, type, cost}.
|
|
*/
|
|
function renderLibraryQuestions(questions) {
|
|
$libraryList.empty(); // Clear previous content or loading indicator
|
|
if (questions.length === 0) {
|
|
$libraryList.html('<p>' + 'No questions found.' + '</p>'); // TODO: Localize
|
|
return;
|
|
}
|
|
|
|
questions.forEach(function(q) {
|
|
// Check if question is already in the current assessment
|
|
const isAdded = currentAssessmentQuestions.includes(q.id);
|
|
const buttonHtml = isAdded
|
|
? '<button class="add-question-to-assessment" data-question-id="' + q.id + '" disabled>Added</button>' // TODO: Localize
|
|
: '<button class="add-question-to-assessment" data-question-id="' + q.id + '">Add</button>'; // TODO: Localize
|
|
|
|
const itemHtml = `
|
|
<div class="library-question-item" data-question-id="${q.id}" data-cost="${q.cost}">
|
|
<span>
|
|
<strong>${q.title || 'Untitled'}</strong> (Type: ${q.type || 'N/A'}) [Cost: ${q.cost || 0}] <!-- TODO: Localize -->
|
|
</span>
|
|
${buttonHtml}
|
|
</div>
|
|
`;
|
|
$libraryList.append(itemHtml);
|
|
});
|
|
|
|
// --- Initialize Draggable on newly rendered library items ---
|
|
initializeDraggable();
|
|
// --- --- --- --- ---
|
|
|
|
}
|
|
|
|
/**
|
|
* Initializes draggable functionality on library items.
|
|
*/
|
|
function initializeDraggable() {
|
|
$libraryList.find('.library-question-item').draggable({
|
|
connectToSortable: '#current-assessment-questions',
|
|
helper: 'clone',
|
|
revert: 'invalid',
|
|
start: function(event, ui) {
|
|
ui.helper.css('width', $(this).width()); // Maintain width during drag
|
|
ui.helper.addClass('dragging-helper'); // Optional: for styling the helper
|
|
},
|
|
stop: function(event, ui) {
|
|
ui.helper.removeClass('dragging-helper');
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Helper to find question data in the currently rendered library list.
|
|
* Note: This is inefficient if the library is paginated. A better approach
|
|
* would be to store added question details in a separate object.
|
|
* @param {number} questionId
|
|
* @returns {object|null} Object with title, cost etc. or null if not found in current view.
|
|
*/
|
|
function findQuestionDataInLibrary(questionId) {
|
|
const $item = $libraryList.find(`.library-question-item[data-question-id="${questionId}"]`);
|
|
if ($item.length) {
|
|
return {
|
|
id: questionId,
|
|
title: $item.find('strong').text(),
|
|
cost: $item.data('cost')
|
|
// Extract type if needed
|
|
};
|
|
}
|
|
return null; // Not found in the current library view
|
|
}
|
|
|
|
|
|
/**
|
|
* Updates the 'Add' buttons in the library list, disabling those for questions
|
|
* already present in the current assessment.
|
|
*/
|
|
function updateLibraryButtons() {
|
|
$libraryList.find('.add-question-to-assessment').each(function() {
|
|
const $button = $(this);
|
|
const questionId = $button.data('question-id');
|
|
if (currentAssessmentQuestions.includes(questionId)) {
|
|
$button.prop('disabled', true).text('Added'); // TODO: Localize
|
|
} else {
|
|
$button.prop('disabled', false).text('Add'); // TODO: Localize
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Saves the current assessment (title and question IDs) via AJAX.
|
|
*/
|
|
function saveAssessment() {
|
|
console.log('Saving assessment...');
|
|
$saveSpinner.css('visibility', 'visible');
|
|
$saveButton.prop('disabled', true);
|
|
$saveStatus.empty().removeClass('success error');
|
|
|
|
const assessmentTitle = $assessmentTitleInput.val();
|
|
if (!assessmentTitle) {
|
|
alert('Assessment title cannot be empty.'); // TODO: Use a nicer notification
|
|
$saveSpinner.css('visibility', 'hidden');
|
|
$saveButton.prop('disabled', false);
|
|
return;
|
|
}
|
|
|
|
$.post(quiztechBuilderData.ajax_url, {
|
|
action: 'quiztech_save_assessment',
|
|
// nonce: quiztechBuilderData.save_nonce, // TODO: Implement nonce verification in PHP
|
|
assessment_id: currentAssessmentId, // Will be null for new assessments
|
|
title: assessmentTitle,
|
|
question_ids: currentAssessmentQuestions
|
|
})
|
|
.done(function(response) {
|
|
if (response.success && response.data.assessment_id) {
|
|
currentAssessmentId = response.data.assessment_id; // Store the ID after saving
|
|
$saveStatus.text(quiztechBuilderData.assessment_saved).addClass('success');
|
|
// Optionally redirect or update UI further
|
|
} else {
|
|
$saveStatus.text(response.data.message || quiztechBuilderData.error_generic).addClass('error');
|
|
}
|
|
})
|
|
.fail(function() {
|
|
$saveStatus.text(quiztechBuilderData.error_generic).addClass('error');
|
|
})
|
|
.always(function() {
|
|
$saveSpinner.css('visibility', 'hidden');
|
|
$saveButton.prop('disabled', false);
|
|
});
|
|
}
|
|
|
|
|
|
// --- Event Handlers ---
|
|
|
|
// Add question from library to current assessment (Handles manual click - less relevant with drag/drop but keep for now)
|
|
$libraryList.on('click', '.add-question-to-assessment', function() {
|
|
const $button = $(this);
|
|
const questionId = $button.data('question-id');
|
|
const $item = $button.closest('.library-question-item');
|
|
|
|
if (!currentAssessmentQuestions.includes(questionId)) {
|
|
// Clone the item, modify it, and append to the current list
|
|
const $clonedItem = $item.clone();
|
|
$clonedItem.removeClass('library-question-item').addClass('selected-question-item');
|
|
$clonedItem.find('.add-question-to-assessment').replaceWith(
|
|
`<button class="remove-question-from-assessment" data-question-id="${questionId}">Remove</button>` // TODO: Localize
|
|
);
|
|
|
|
// Remove placeholder if it exists
|
|
$currentList.find('p').remove();
|
|
|
|
$currentList.append($clonedItem);
|
|
|
|
// Update state and UI
|
|
updateStateFromDOM();
|
|
updateTotalCost();
|
|
updateLibraryButtons();
|
|
}
|
|
});
|
|
|
|
// Remove question from current assessment
|
|
$currentList.on('click', '.remove-question-from-assessment', function() {
|
|
const $button = $(this);
|
|
const questionId = $button.data('question-id');
|
|
$button.closest('.selected-question-item').remove(); // Remove item from DOM
|
|
|
|
// Update state and UI
|
|
updateStateFromDOM();
|
|
updateTotalCost();
|
|
updateLibraryButtons(); // Re-enable the 'Add' button in the library
|
|
|
|
// Add placeholder if list becomes empty
|
|
if ($currentList.children().length === 0) {
|
|
$currentList.html('<p>' + 'No questions added yet.' + '</p>'); // TODO: Localize
|
|
}
|
|
});
|
|
|
|
// Save assessment button click
|
|
$saveButton.on('click', function(e) {
|
|
e.preventDefault();
|
|
saveAssessment();
|
|
});
|
|
|
|
// TODO: Add handlers for search/filter/pagination controls when implemented.
|
|
// --- Initialize Sortable ---
|
|
$currentList.sortable({
|
|
placeholder: "ui-state-highlight", // Class for placeholder styling
|
|
axis: 'y', // Allow vertical sorting only
|
|
update: function(event, ui) {
|
|
// This fires when sorting stops *within* the list OR when an item is received
|
|
console.log('Sortable Updated');
|
|
updateStateFromDOM();
|
|
updateTotalCost(); // Recalculate cost based on new order/items
|
|
},
|
|
receive: function(event, ui) {
|
|
// This fires specifically when a draggable is dropped onto the sortable
|
|
console.log('Sortable Received');
|
|
const questionId = ui.item.data('question-id');
|
|
const $originalItem = ui.item; // This is the helper clone that becomes the list item
|
|
|
|
// Check for duplicates based on the state *before* this item was potentially added by 'update'
|
|
const existingIds = $currentList.find('.selected-question-item').map(function() {
|
|
return $(this).data('question-id');
|
|
}).get();
|
|
let count = 0;
|
|
existingIds.forEach(id => { if (id === questionId) count++; });
|
|
|
|
if (count > 1) { // If the dropped item creates a duplicate
|
|
console.log('Duplicate detected, cancelling drop');
|
|
ui.sender.sortable('cancel'); // Cancel the drop/receive operation
|
|
$originalItem.remove(); // Remove the duplicate item that was briefly added
|
|
alert('This question is already in the assessment.'); // TODO: Nicer notification
|
|
return; // Stop further processing for this event
|
|
}
|
|
|
|
// Transform the received item (clone) into a selected item
|
|
$originalItem.removeClass('library-question-item dragging-helper').addClass('selected-question-item');
|
|
$originalItem.find('.add-question-to-assessment').replaceWith(
|
|
`<button class="remove-question-from-assessment" data-question-id="${questionId}">Remove</button>` // TODO: Localize
|
|
);
|
|
$originalItem.css({ width: '', height: '' }); // Reset helper styles
|
|
|
|
// Remove placeholder if it exists
|
|
$currentList.find('p').remove();
|
|
|
|
// Update state and UI (update event will also fire, but good to be explicit)
|
|
updateStateFromDOM();
|
|
updateTotalCost();
|
|
updateLibraryButtons(); // Disable the 'Add' button in the library
|
|
}
|
|
}).disableSelection(); // Prevent text selection while dragging
|
|
|
|
// --- Initial Load ---
|
|
fetchLibraryQuestions(); // Load initial questions into the library (will also init draggable)
|
|
// renderCurrentAssessment(); // Don't render initially, sortable handles it
|
|
|
|
// Add placeholder initially if list is empty
|
|
if ($currentList.children().length === 0) {
|
|
$currentList.html('<p>' + 'No questions added yet.' + '</p>'); // TODO: Localize
|
|
}
|
|
|
|
}); |