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 // --- Functions --- /** * Updates the total cost display based on the questions currently in the assessment list. */ function updateTotalCost() { console.log('--- Updating Total Cost ---'); // Debug let totalCost = 0; $currentList.find('.selected-question-item').each(function(index) { const cost = parseInt($(this).data('cost') || 0); console.log(`Item ${index}: data-cost = ${$(this).data('cost')}, parsed cost = ${cost}`); // Debug totalCost += cost; }); console.log('Calculated Total Cost:', totalCost); // Debug $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('

' + quiztechBuilderData.loading_questions + '

'); // 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('

' + (response.data.message || quiztechBuilderData.error_generic) + '

'); } }) .fail(function() { $libraryList.html('

' + quiztechBuilderData.error_generic + '

'); }) .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('

' + 'No questions found.' + '

'); // 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 ? '' // TODO: Localize : ''; // TODO: Localize const itemHtml = `
${q.title || 'Untitled'} (Type: ${q.type || 'N/A'}) [Cost: ${q.cost || 0}] ${buttonHtml}
`; $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); }); } /** * Fetches data for an existing assessment and populates the builder. * @param {number} assessmentId The ID of the assessment to load. */ function loadExistingAssessment(assessmentId) { console.log('Loading assessment:', assessmentId); // Show some loading indicator maybe? $saveStatus.text('Loading assessment data...').removeClass('success error'); // TODO: Localize $.post(quiztechBuilderData.ajax_url, { action: 'quiztech_fetch_assessment_data', // nonce: quiztechBuilderData.fetch_nonce, // TODO: Implement nonce verification in PHP assessment_id: assessmentId }) .done(function(response) { if (response.success && response.data) { currentAssessmentId = assessmentId; // Set the ID in state $assessmentTitleInput.val(response.data.title || ''); // Clear placeholder and render questions $currentList.empty(); if (response.data.questions && response.data.questions.length > 0) { response.data.questions.forEach(function(q) { const itemHtml = `
${q.title || 'Untitled'} (Type: ${q.type || 'N/A'}) [Cost: ${q.cost || 0}]
`; $currentList.append(itemHtml); }); } else { // Add placeholder if no questions were returned $currentList.html('

' + 'No questions added yet.' + '

'); // TODO: Localize } // Update state and UI based on loaded data updateStateFromDOM(); updateTotalCost(); updateLibraryButtons(); // Disable 'Add' buttons for loaded questions $saveStatus.text('Assessment loaded.').addClass('success'); // TODO: Localize setTimeout(() => $saveStatus.empty().removeClass('success'), 3000); // Clear status after a delay } else { $saveStatus.text(response.data.message || 'Error loading assessment.').addClass('error'); // TODO: Localize // Maybe redirect or show a more prominent error? } }) .fail(function() { $saveStatus.text('Error loading assessment.').addClass('error'); // TODO: Localize }); } // --- 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( `` // 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('

' + 'No questions added yet.' + '

'); // 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( `` // TODO: Localize ); $originalItem.css({ width: '', height: '' }); // Reset helper styles // Remove placeholder if it exists $currentList.find('p').remove(); // Update state and UI updateLibraryButtons(); // Disable the 'Add' button in the library immediately // Explicitly update state and cost *after* DOM manipulation in receive updateStateFromDOM(); updateTotalCost(); } }).disableSelection(); // Prevent text selection while dragging // --- Check for Edit Mode on Load --- const urlParams = new URLSearchParams(window.location.search); const editAssessmentId = urlParams.get('assessment_id'); if (editAssessmentId && !isNaN(parseInt(editAssessmentId))) { loadExistingAssessment(parseInt(editAssessmentId)); } // --- 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('

' + 'No questions added yet.' + '

'); // TODO: Localize } });