/* SURVEY CODE (CODE FOR BUILDING SURVEY) */ // Select DOM elements for question types and survey area const questionTypes = document.getElementById('question-types'); const surveyArea = document.getElementById('survey-area'); let savestatus = false; window.addEventListener("beforeunload", function (event) { const ques = document.querySelectorAll('#survey-area .question'); if (ques.length > 0 && savestatus == false) { event.preventDefault(); } }); const sortableSurvey = new Sortable(surveyArea, { group: 'survey', // Enable drag and drop within this group animation: 150, // Animation speed when moving elements handle: '.drag-handle', // Set the handle for dragging onAdd: function (evt) { // This event is triggered when a new question is added from the list const questionType = evt.item.dataset.type; // Get the type of question const newQuestion = createQuestion(questionType); // Create a new question element evt.item.replaceWith(newQuestion); // Replace placeholder with actual question }, }); // Initialize Sortable.js for the question types container to enable dragging new Sortable(questionTypes, { group: { name: 'survey', pull: 'clone', put: false, }, sort: false, animation: 150, filter: ".no-drag, button", // Exclude buttons from dragging onStart: function(evt) { if (evt.item.matches("button")) { evt.preventDefault(); // Prevent dragging } } }); function createQuestion(type) { savestatus = false; const question = document.createElement('div'); // Create a div for the question question.className = 'question'; // Set class for styling question.dataset.type = type; // Keep the data-type // Create the custom handle const dragHandle = document.createElement('span'); dragHandle.className = 'drag-handle'; dragHandle.innerHTML = '⋮'; // A simple icon for the drag handle dragHandle.style.cursor = 'move'; // Show the grab cursor for the handle const deleteButton = document.createElement('button'); deleteButton.className = 'btn btn-danger btn-sm'; deleteButton.textContent = 'Delete'; deleteButton.addEventListener('click', () => question.remove()); const questionLabel = document.createElement('strong'); let inputField = null; if (type === 'new-page' || type === 'add-video' || type === 'add-image' || type === 'add-text') { // For the 'new-page' component, only display "New Page" and no input fields if (type === 'new-page') { questionLabel.textContent = 'New Page'; question.appendChild(dragHandle); // Add the custom handle question.appendChild(questionLabel); question.appendChild(deleteButton); // Append the delete button } else if (type === 'add-video') { questionLabel.textContent = 'Add Video'; const questionText = document.createElement('input'); questionText.type = 'text'; questionText.placeholder = 'Enter your video link here'; questionText.className = 'form-control mb-2'; question.appendChild(dragHandle); // Add the custom handle question.appendChild(questionLabel); question.appendChild(deleteButton); // Append the delete button question.appendChild(questionText); } else if (type === 'add-image') { questionLabel.textContent = 'Add Image'; const questionText = document.createElement('input'); questionText.type = 'text'; questionText.placeholder = 'Enter your image link here'; questionText.className = 'form-control mb-2'; question.appendChild(dragHandle); // Add the custom handle question.appendChild(questionLabel); question.appendChild(deleteButton); // Append the delete button question.appendChild(questionText); } else if (type === 'add-text') { questionLabel.textContent = 'Add Text'; const questionText = document.createElement('input'); questionText.type = 'text'; questionText.placeholder = 'Enter the text you want to appear here'; questionText.className = 'form-control mb-2'; question.appendChild(dragHandle); // Add the custom handle question.appendChild(questionLabel); question.appendChild(deleteButton); // Append the delete button question.appendChild(questionText); } } else { if (type === 'multiple-choice') { questionLabel.textContent = 'Multiple-choice Question'; } else if (type === 'multiselect') { questionLabel.textContent = 'Multiselect Question'; } else if (type === 'slider') { questionLabel.textContent = 'Slider Question'; } else if (type === 'text-input') { questionLabel.textContent = 'Text-Input Question'; } else if (type === 'selectbox') { questionLabel.textContent = 'Select Box Question'; } else if (type === 'likert') { questionLabel.textContent = 'Likert Question' } const questionText = document.createElement('input'); questionText.type = 'text'; questionText.placeholder = 'Enter your question here'; questionText.className = 'form-control mb-2'; // Append the question label before the text input question.appendChild(dragHandle); // Add the custom handle question.appendChild(questionLabel); question.appendChild(deleteButton); // Append the delete button question.appendChild(questionText); // Append the text input to the question div // Handle specific question types if (type === 'multiple-choice') { inputField = createMultipleChoiceOptions(); } else if (type === 'multiselect') { inputField = createMultiselectOptions(); } else if (type === 'slider') { inputField = createSliderOptions(); } else if (type === 'selectbox') { inputField = createSelecBoxtOptions(); } } // Append the drag handle and delete button if (inputField) { question.appendChild(inputField); // Append the input field if applicable } return question; } // Helper function for multiple-choice options (unchanged) function createMultipleChoiceOptions() { const inputField = document.createElement('div'); function createOptionInput(placeholder) { const optionContainer = document.createElement('div'); optionContainer.className = 'input-group mb-1'; const optionInput = document.createElement('input'); optionInput.type = 'text'; optionInput.placeholder = placeholder; optionInput.className = 'form-control'; const deleteOptionButton = document.createElement('button'); deleteOptionButton.className = 'btn btn-danger btn-sm'; deleteOptionButton.textContent = 'X'; deleteOptionButton.addEventListener('click', () => optionContainer.remove()); const inputGroupAppend = document.createElement('div'); inputGroupAppend.className = 'input-group-append'; inputGroupAppend.appendChild(deleteOptionButton); optionContainer.appendChild(optionInput); optionContainer.appendChild(inputGroupAppend); return optionContainer; } inputField.appendChild(createOptionInput('Option 1')); inputField.appendChild(createOptionInput('Option 2')); const addOptionButton = document.createElement('button'); addOptionButton.className = 'btn btn-secondary btn-sm mb-2'; addOptionButton.textContent = 'Add Option'; addOptionButton.addEventListener('click', () => { const optionCount = inputField.querySelectorAll('.input-group').length + 1; inputField.insertBefore(createOptionInput(`Option ${optionCount}`), addOptionButton); }); inputField.appendChild(addOptionButton); return inputField; } // Helper function for multiselect options function createMultiselectOptions() { const inputField = document.createElement('div'); function createMultiselectOption(placeholder) { const multiselectContainer = document.createElement('div'); multiselectContainer.className = 'input-group2 mb-1'; const labelInput = document.createElement('input'); labelInput.type = 'text'; labelInput.className = 'form-control-sm'; labelInput.placeholder = placeholder; const deleteOptionButton = document.createElement('button'); deleteOptionButton.className = 'btn btn-danger btn-sm'; deleteOptionButton.textContent = 'X'; deleteOptionButton.addEventListener('click', () => multiselectContainer.remove()); multiselectContainer.appendChild(labelInput); multiselectContainer.appendChild(deleteOptionButton); return multiselectContainer; } inputField.appendChild(createMultiselectOption('Option 1')); inputField.appendChild(createMultiselectOption('Option 2')); const addmultiselectButton = document.createElement('button'); addmultiselectButton.className = 'btn btn-secondary btn-sm mb-2'; addmultiselectButton.textContent = 'Add Option'; addmultiselectButton.addEventListener('click', () => { const optionCount = inputField.querySelectorAll('.input-group2').length + 1; inputField.insertBefore(createMultiselectOption(`Option ${optionCount}`), addmultiselectButton); }); inputField.appendChild(addmultiselectButton); return inputField; } // Helper function for slider options // Helper function to create slider options function createSliderOptions() { const inputField = document.createElement('div'); // Create a container for the slider labels const sliderLabels = document.createElement('div'); sliderLabels.className = 'd-flex justify-content-between mb-2'; inputField.appendChild(sliderLabels); // Create input fields for the min and max values of the slider with labels const minMaxContainer = document.createElement('div'); minMaxContainer.className = 'd-flex justify-content-between mb-2'; // Min Value Label and Input const minValueWrapper = document.createElement('div'); const minValueLabel = document.createElement('label'); minValueLabel.innerText = 'Min Value'; minValueLabel.className = 'form-label'; const minValueInput = document.createElement('input'); minValueInput.type = 'number'; minValueInput.className = 'form-control'; minValueInput.placeholder = 'Min Value'; minValueInput.value = 0; // Default min value minValueWrapper.appendChild(minValueLabel); minValueWrapper.appendChild(minValueInput); // Max Value Label and Input const maxValueWrapper = document.createElement('div'); const maxValueLabel = document.createElement('label'); maxValueLabel.innerText = 'Max Value'; maxValueLabel.className = 'form-label'; const maxValueInput = document.createElement('input'); maxValueInput.type = 'number'; maxValueInput.className = 'form-control'; maxValueInput.placeholder = 'Max Value'; maxValueInput.value = 10; // Default max value maxValueWrapper.appendChild(maxValueLabel); maxValueWrapper.appendChild(maxValueInput); minMaxContainer.appendChild(minValueWrapper); minMaxContainer.appendChild(maxValueWrapper); // Create a warning message for invalid input const warningMessage = document.createElement('small'); warningMessage.style.color = 'red'; warningMessage.style.display = 'none'; warningMessage.innerText = 'Max value must be greater than Min value'; inputField.appendChild(minMaxContainer); inputField.appendChild(warningMessage); // Add event listeners to check max > min validation function validateMinMax() { const min = parseFloat(minValueInput.value); const max = parseFloat(maxValueInput.value); if (max <= min) { warningMessage.style.display = 'block'; // Show the warning message } else { warningMessage.style.display = 'none'; // Hide the warning message } } // Listen for changes in the min and max input fields minValueInput.addEventListener('input', validateMinMax); maxValueInput.addEventListener('input', validateMinMax); return inputField; } // Helper function for multiselect options function createSelecBoxtOptions() { const inputField = document.createElement('div'); function createSelectBoxtOption(placeholder) { const multiselectContainer = document.createElement('div'); multiselectContainer.className = 'input-group2 mb-1'; const labelInput = document.createElement('input'); labelInput.type = 'text'; labelInput.className = 'form-control-sm'; labelInput.placeholder = placeholder; const deleteOptionButton = document.createElement('button'); deleteOptionButton.className = 'btn btn-danger btn-sm'; deleteOptionButton.textContent = 'X'; deleteOptionButton.addEventListener('click', () => multiselectContainer.remove()); multiselectContainer.appendChild(labelInput); multiselectContainer.appendChild(deleteOptionButton); return multiselectContainer; } inputField.appendChild(createSelectBoxtOption('Option 1')); inputField.appendChild(createSelectBoxtOption('Option 2')); const addmultiselectButton = document.createElement('button'); addmultiselectButton.className = 'btn btn-secondary btn-sm mb-2'; addmultiselectButton.textContent = 'Add Option'; addmultiselectButton.addEventListener('click', () => { const optionCount = inputField.querySelectorAll('.input-group2').length + 1; inputField.insertBefore(createSelectBoxtOption(`Option ${optionCount}`), addmultiselectButton); }); inputField.appendChild(addmultiselectButton); return inputField; } /* SAVING CODE (CODE FOR SAVING SURVEY TO PYTHON FILE) */ // Function to save the survey as a text file // Function to save the survey as a Python file const exportModalElement = document.getElementById('exportModal'); const githubModalElement = document.getElementById('githubModal'); const hfModalElement = document.getElementById('hfModal'); const exportButton = document.getElementById('save-survey'); // Event listener for opening the export modal document.getElementById('save-survey').addEventListener('click', () => { const exportModal = new bootstrap.Modal(exportModalElement, { backdrop: 'static', keyboard: false, focus: true }); exportModalElement.removeAttribute('inert'); // Allow interaction exportModal.show(); }); // Event listener for local download // Prompts the user to enter their sepolia URL and private key document.getElementById('download-btn').addEventListener('click', () => { const surveyPythonCode = buildSurveyPythonCode(); // Get Python survey code downloadSurvey(surveyPythonCode); // Trigger local download savestatus = true; const exportModal = bootstrap.Modal.getInstance(exportModalElement); exportModal.hide(); // Close the modal }); // Event listener for opening the GitHub modal document.getElementById('upload-btn-git').addEventListener('click', () => { const exportModal = bootstrap.Modal.getInstance(exportModalElement); exportModal.hide(); const githubModal = new bootstrap.Modal(githubModalElement, { backdrop: 'static', keyboard: false, focus: true }); githubModalElement.removeAttribute('inert'); // Allow interaction githubModal.show(); }); document.getElementById('upload-btn-hf').addEventListener('click', () => { const exportModal = bootstrap.Modal.getInstance(exportModalElement); exportModal.hide(); const hfModal = new bootstrap.Modal(hfModalElement, { backdrop: 'static', keyboard: false, focus: true }); hfModalElement.removeAttribute('inert'); // Allow interaction hfModal.show(); }); // Event listener for uploading to GitHub document.getElementById('submit-upload-git').addEventListener('click', async () => { const repoId = document.getElementById('repo-id-git').value.trim(); const accessToken = document.getElementById('access-token-git').value.trim(); const submitButton = document.getElementById('submit-upload-git'); const loadingSpinner = document.getElementById('loading-spinner-git'); // Reference to loading spinner if (!repoId || !accessToken) { alert("Please enter both a repository name and an access token."); return; } if ((document.querySelectorAll('#survey-area .question')).length == 0) { alert("No Content added to Canvas. Please add Components before uploading to Github"); return; } // Show loading spinner and disable the submit button submitButton.disabled = true; loadingSpinner.style.display = "inline-block"; try { const surveyPythonCode = buildSurveyPythonCode(); // Get survey Python code const requirements = `web3==7.8.0\nrequests==2.31.0\nstreamlit==1.28.2`; const [username, repoName] = repoId.split("/"); if (!username || !repoName) { alert("Invalid repository format. Use 'username/repository'."); } else { await uploadReqToGitHub(repoId, accessToken, requirements); await uploadToGitHub(repoId, accessToken, surveyPythonCode); } // Close the GitHub modal after successful upload const githubModal = bootstrap.Modal.getInstance(document.getElementById('githubModal')); githubModal.hide(); } catch (error) { console.error("Upload failed:", error); alert("Failed to upload. Make sure your repository exists and your access token is correct"); } finally { // Hide loading spinner and re-enable the button after upload completes loadingSpinner.style.display = "none"; submitButton.disabled = false; } }); // Ensure modals use inert when hidden exportModalElement.addEventListener('hidden.bs.modal', () => { exportModalElement.setAttribute('inert', 'true'); exportButton.focus(); }); githubModalElement.addEventListener('hidden.bs.modal', () => { githubModalElement.setAttribute('inert', 'true'); // Disable interaction }); hfModalElement.addEventListener('hidden.bs.modal', () => { hfModalElement.setAttribute('inert', 'true'); // Disable interaction }); // Function to trigger local download function downloadSurvey(content) { const blob = new Blob([content], { type: 'text/plain' }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "app.py"; document.body.appendChild(a); a.click(); document.body.removeChild(a); } // Function to upload survey to GitHub async function uploadToGitHub(repoId, accessToken, content) { const [username, repoName] = repoId.split("/"); const encodedContent = btoa(unescape(encodeURIComponent(content))); const fileName = "survey.py"; const apiUrl = `https://api.github.com/repos/${username}/${repoName}/contents/${fileName}`; try { // Check if file already exists let sha = null; const existingFile = await fetch(apiUrl, { headers: { Authorization: `Bearer ${accessToken}` } }); if (existingFile.ok) { const fileData = await existingFile.json(); sha = fileData.sha; } // Upload or update file const response = await fetch(apiUrl, { method: "PUT", headers: { "Authorization": `Bearer ${accessToken}`, "Content-Type": "application/json" }, body: JSON.stringify({ message: "Upload survey script", content: encodedContent, branch: "main", sha: sha // Include sha if updating an existing file }) }); if (!response.ok) throw new Error(`GitHub API error: ${response.statusText}`); savestatus = true; const result = await response.json(); alert(`Survey uploaded successfully: ${result.content.html_url}`); } catch (error) { console.error("Upload failed:", error); alert("Failed to upload. Make sure your repository exists and your access token is correct"); } } async function uploadReqToGitHub(repoId, accessToken, content) { const [username, repoName] = repoId.split("/"); const encodedContent = btoa(unescape(encodeURIComponent(content))); const fileName = "requirements.txt"; const apiUrl = `https://api.github.com/repos/${username}/${repoName}/contents/${fileName}`; try { // Check if file already exists let sha = null; const existingFile = await fetch(apiUrl, { headers: { Authorization: `Bearer ${accessToken}` } }); if (existingFile.ok) { const fileData = await existingFile.json(); sha = fileData.sha; } // Upload or update file const response = await fetch(apiUrl, { method: "PUT", headers: { "Authorization": `Bearer ${accessToken}`, "Content-Type": "application/json" }, body: JSON.stringify({ message: "Upload requirements file", content: encodedContent, branch: "main", sha: sha // Include sha if updating an existing file }) }); if (!response.ok) throw new Error(`GitHub API error: ${response.statusText}`); savestatus = true; const result = await response.json(); } catch (error) { console.error("Error:", error); } } function buildSurveyPythonCode() { let pythonCode = ''; // Initialize the Python code content const questions = document.querySelectorAll('#survey-area .question'); let questionIndex = 1; // Track question index let pageIndex = 1; // Track page index pythonCode += formatPreCode(questions); let currentPageQuestions = []; // List to hold the questions for each page let currentPagIndicies = []; // List to hold the question indices for each page questions.forEach((question) => { const questionType = question.querySelector('strong').textContent.replace(' Question', ''); // Detect if it's a New Page component if (questionType.toLowerCase() === 'new page') { if (pageIndex == 1) { pythonCode += formatFirstPageCode(currentPagIndicies, currentPageQuestions) } else { pythonCode += formatPostCode(currentPagIndicies, currentPageQuestions, pageIndex); // Pass the current page's questions for validation } pageIndex++; // Increment the page index pythonCode += `elif st.session_state["current_page"] == ${pageIndex}:\n\n`; // Set up the next page logic currentPageQuestions = []; // Reset for the new page currentPagIndicies = []; } else if (questionType.toLowerCase() === 'add video' || questionType.toLowerCase() === 'add image' || questionType.toLowerCase() === 'add text') { const questionText = question.querySelector('input[type="text"]').value; if (questionType.toLowerCase() === 'add video') { pythonCode += formatVideoPython(questionText); } else if (questionType.toLowerCase() === 'add image') { pythonCode += formatImagePython(questionText); } else if (questionType.toLowerCase() === 'add text') { pythonCode += formatTextPython(questionText); } } else { const questionText = question.querySelector('input[type="text"]').value; // Handle multiple-choice questions for now if (questionType.toLowerCase() === 'multiple-choice') { pythonCode += formatMultipleChoicePython(questionText, question, questionIndex); pythonCode += ` st.markdown("""

""", unsafe_allow_html=True)\n\n\n`; } else if (questionType.toLowerCase() === 'text-input') { pythonCode += formatTextQuestionPython(questionText, question, questionIndex); pythonCode += ` st.markdown("""

""", unsafe_allow_html=True)\n\n\n`; } else if (questionType.toLowerCase() === 'multiselect') { pythonCode += formatMultiselectPython(questionText, question, questionIndex); pythonCode += ` st.markdown("""

""", unsafe_allow_html=True)\n\n\n`; } else if (questionType.toLowerCase() === 'slider') { pythonCode += formatSliderQuestionsPython(questionText, question, questionIndex); pythonCode += ` st.markdown("""

""", unsafe_allow_html=True)\n\n\n`; } else if (questionType.toLowerCase() === 'select box') { pythonCode += formatSelectBoxPython(questionText, question, questionIndex); pythonCode += ` st.markdown("""

""", unsafe_allow_html=True)\n\n\n`; } else if (questionType.toLowerCase() === 'likert') { pythonCode += formatlikert(questionText, question, questionIndex); pythonCode += ` st.markdown("""

""", unsafe_allow_html=True)\n\n\n`; } // Add the question index to the current page's questions list currentPagIndicies.push(questionIndex); currentPageQuestions.push(questionType); questionIndex++; // Increment the question index } }); // Finalize the last page's code pythonCode += formatPostCode(currentPagIndicies, currentPageQuestions, pageIndex); pythonCode += formatFinalPageCode(questions, pageIndex); return pythonCode; } function formatFinalPageCode(questions, pageCount) { pythonCode = ``; pythonCode += `elif st.session_state["current_page"] == ${pageCount+1}: # Last Page\n` pythonCode += ` st.markdown('

Thank you for participating!
Click on the button below to submit your answers.

', unsafe_allow_html=True)\n`; pythonCode += ` st.button('Submit Responses', disabled = st.session_state["disabled"], on_click = button_disable)\n`; pythonCode += ` if st.session_state["disabled"]:\n`; pythonCode += ' with st.spinner(r"$\\textsf{\\normalsize Storing data on IPFS and Ethereum. This operation might take a few minutes. Please wait to receive your confirmation code!}$"):\n'; pythonCode += ` try:\n`; pythonCode += ` response = {\n`; let questionNumber = 1; questions.forEach((question) => { let questionType = question.querySelector('strong').textContent.replace(' Question', ''); if (questionType.toLowerCase() === 'multiple-choice' || questionType.toLowerCase() === 'text-input' || questionType.toLowerCase() === 'multiselect' || questionType.toLowerCase() === 'slider' || questionType.toLowerCase() === 'select box' || questionType.toLowerCase() === 'likert') { if (questionType.toLowerCase() === 'multiple-choice' || questionType.toLowerCase() === 'likert' || questionType.toLowerCase() === 'select box') { pythonCode += ` "Q${questionNumber}": Q${questionNumber}_radio_options[st.session_state["Q${questionNumber}"]],\n` } else { pythonCode += ` "Q${questionNumber}": st.session_state["Q${questionNumber}"],\n` } questionNumber++; } }); pythonCode += ` }\n` pythonCode += ` submission(response)\n` pythonCode += ` except Exception as e:\n`; pythonCode += ` print(e)\n`; pythonCode += ` st.error(f'An error ocurred. Here is the error message: {e}', icon="🚨")\n\n`; pythonCode += ` print("Success!")\n\n`; pythonCode += ` if st.button('Back'):\n` pythonCode += ` st.session_state["current_page"] -= 1\n` pythonCode += ` st.rerun()\n` pythonCode += ` st.progress(st.session_state["current_page"]/total_number_pages, text="Progress")\n`; return pythonCode; } function formatFirstPageCode(questionIndices, questions) { let pythonCode = ``; pythonCode += ` placeholder = st.empty()\n\n`; pythonCode += " if st.button('Next', key='next_button_page_1'):\n"; // Check that all questions on the page are answered pythonCode += ` all_answered = True\n`; questions.forEach((question, i) => { const index = questionIndices[i]; // Get the corresponding index for this question pythonCode += ` if st.session_state["Q${index}"] == None or st.session_state["Q${index}"] == []:\n`; pythonCode += ` all_answered = False\n`; }); pythonCode += ` if all_answered:\n`; pythonCode += ` st.session_state["current_page"] += 1\n`; pythonCode += ` st.rerun()\n`; pythonCode += ` else:\n`; pythonCode += ` with placeholder.container():\n`; pythonCode += ` st.warning("Please answer all the questions on this page.", icon="⚠️")\n\n`; pythonCode += ` st.progress(st.session_state["current_page"]/total_number_pages, text="Progress")\n\n\n`; return pythonCode; } function formatPostCode(questionIndices, questions, pageIndex) { let pythonCode = ''; if (pageIndex != 1) { pythonCode += ' placeholder = st.empty()\n\n'; pythonCode += ' col1, col2 = st.columns(2)\n'; pythonCode += ' with col1:\n'; pythonCode += " if st.button('Back'):\n"; pythonCode += ' st.session_state["current_page"] -= 1\n'; pythonCode += ' st.rerun()\n'; pythonCode += ' with col2:\n'; pythonCode += ` if st.button('Next'):\n`; // Check that all questions on the page are answered pythonCode += ' all_answered = True\n'; questions.forEach((question, i) => { const index = questionIndices[i]; // Get the corresponding index for this question pythonCode += ` if st.session_state["Q${index}"] == None or st.session_state["Q${index}"] == []:\n`; pythonCode += ` all_answered = False\n`; }); pythonCode += ' if all_answered:\n'; pythonCode += ' st.session_state["current_page"] += 1\n'; pythonCode += ' st.rerun()\n'; pythonCode += ' else:\n'; pythonCode += ' with placeholder.container():\n'; pythonCode += ' st.warning("Please answer all the questions on this page.", icon="⚠️")\n\n'; pythonCode += ' st.progress(st.session_state["current_page"]/total_number_pages, text="Progress")\n\n\n'; } else { pythonCode += ' placeholder = st.empty()\n\n'; pythonCode += ` if st.button('Next'):\n`; pythonCode += ' all_answered = True\n'; questions.forEach((question, i) => { const index = questionIndices[i]; // Get the corresponding index for this question pythonCode += ` if st.session_state["Q${index}"] == None or st.session_state["Q${index}"] == []:\n`; pythonCode += ` all_answered = False\n`; }); pythonCode += ' if all_answered:\n'; pythonCode += ' st.session_state["current_page"] += 1\n'; pythonCode += ' st.rerun()\n'; pythonCode += ' else:\n'; pythonCode += ' with placeholder.container():\n'; pythonCode += ' st.warning("Please answer all the questions on this page.", icon="⚠️")\n\n'; pythonCode += ' st.progress(st.session_state["current_page"]/total_number_pages, text="Progress")\n\n\n'; } return pythonCode; } function formatPreCode(questions) { pythonCode = ''; const surveyTitle = document.getElementById('survey-title').value.trim(); const surveyDescription = document.getElementById('survey-description').value.trim(); totalPages = 1; totalQuestions = 0; questions.forEach(question => { const questionType = question.querySelector('strong').textContent.replace(' Question', ''); if (questionType.toLowerCase() === 'new page') { totalPages++; } else if (questionType.toLowerCase() === 'add video' || questionType.toLowerCase() === 'add image' || questionType.toLowerCase() === 'add text') { } else { totalQuestions++; } }); // Add necessary imports pythonCode += `import streamlit as st\nimport requests\nimport json\nimport web3\n\n`; pythonCode += 'abi = [\n' pythonCode += '\t{\n' pythonCode += '\t\t"anonymous": False,\n' pythonCode += '\t\t"inputs": [\n' pythonCode += '\t\t\t{\n' pythonCode += '\t\t\t\t"indexed": False,\n' pythonCode += '\t\t\t\t"internalType": "string",\n' pythonCode += '\t\t\t\t"name": "ipfsHash",\n' pythonCode += '\t\t\t\t"type": "string"\n' pythonCode += '\t\t\t}\n' pythonCode += '\t\t],\n' pythonCode += '\t\t"name": "Store",\n' pythonCode += '\t\t"type": "event"\n' pythonCode += '\t},\n' pythonCode += '\t{\n' pythonCode += '\t\t"inputs": [],\n' pythonCode += '\t\t"name": "getHashes",\n' pythonCode += '\t\t"outputs": [\n' pythonCode += '\t\t\t{\n' pythonCode += '\t\t\t\t"internalType": "string[]",\n' pythonCode += '\t\t\t\t"name": "",\n' pythonCode += '\t\t\t\t"type": "string[]"\n' pythonCode += '\t\t\t}\n' pythonCode += '\t\t],\n' pythonCode += '\t\t"stateMutability": "view",\n' pythonCode += '\t\t"type": "function"\n' pythonCode += '\t},\n' pythonCode += '\t{\n' pythonCode += '\t\t"inputs": [\n' pythonCode += '\t\t\t{\n' pythonCode += '\t\t\t\t"internalType": "string",\n' pythonCode += '\t\t\t\t"name": "ipfsHash",\n' pythonCode += '\t\t\t\t"type": "string"\n' pythonCode += '\t\t\t}\n' pythonCode += '\t\t],\n' pythonCode += '\t\t"name": "storeHash",\n' pythonCode += '\t\t"outputs": [],\n' pythonCode += '\t\t"stateMutability": "nonpayable",\n' pythonCode += '\t\t"type": "function"\n' pythonCode += '\t}\n' pythonCode += ']\n' pythonCode += `\n# function that uploads the results to ipfs def upload_json_to_ipfs(data): try: url = "https://api.pinata.cloud/pinning/pinJSONToIPFS" print("starting upload to ipfs") # check for JWT secret if "PinataJWT" not in st.secrets: st.write("No JWT secret found, please add your JWT in a secret titled \\"PinataJWT\\"") return jwt_token = st.secrets["PinataJWT"].strip() headers = { "Authorization": f"Bearer {jwt_token}", "Content-Type": "application/json" } # Convert Python dictionary to JSON string response = requests.post(url, headers=headers, json=data) if response.status_code == 200: # Print the IPFS hash from the successful response ipfs_hash = response.json().get("IpfsHash") return ipfs_hash else: st.write(f"Failed to upload JSON. Status code: {response.status_code}") st.write(response.text) return None except Exception as e: st.write(f"Error uploading to Pinata: {e}") # function that uploads to blockchain def upload_to_blockchain(ipfs_hash): print("starting blockchain upload") w3 = web3.Web3(web3.HTTPProvider(st.secrets["infura"])) if not w3.is_connected(): raise RuntimeError("Failed to connect to Ethereum network") try: contract_address = st.secrets["ContractAddress"] checksum_address = w3.to_checksum_address(contract_address) except Exception as e: raise ValueError(f"Invalid contract address: {contract_address} Error: {e}") try: assert isinstance(abi, list), "ABI must be a list" except Exception as e: raise ValueError(f"ABI format error: {e}") contract = w3.eth.contract(address=checksum_address, abi=abi) # Build transaction call_function = contract.functions.storeHash(ipfs_hash).build_transaction({ "chainId": 11155111, "from": st.secrets["EthWallet"], "nonce": w3.eth.get_transaction_count(st.secrets["EthWallet"]), "gas": 300000, "gasPrice": w3.to_wei("10", "gwei") }) # Sign transaction signed_tx = w3.eth.account.sign_transaction(call_function, private_key=st.secrets["pk"]) # Send transaction tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) # Wait for transaction receipt tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) print("Transaction successful!") print("ETH Tx Hash:", tx_receipt.transactionHash.hex()) st.success('Data successfully stored. Thank you for taking the survey!', icon="✅") st.info(f'The Ethereum hash is: {tx_receipt.logs[0].transactionHash.hex()}', icon="ℹ️") return tx_receipt.transactionHash.hex() def get_ipfs_hashes(): w3 = web3.Web3(web3.HTTPProvider(st.secrets["infura"])) # Create an instance of the contract contract = w3.eth.contract(address=st.secrets["ContractAddress"], abi=abi) # Call the getHashes function try: ipfs_hashes = contract.functions.getHashes().call() return ipfs_hashes except Exception as e: st.write(f"Error retrieving hashes: {e}") return [] def retreive_ipfs_hash_data(hashes): results = [] for ipfs_hash in hashes: url = f"https://gateway.pinata.cloud/ipfs/{ipfs_hash}" try: response = requests.get(url) if response.status_code == 200: data = response.json() results.append({"hash": ipfs_hash, "data": data}) else: results.append({"hash": ipfs_hash, "error": f"Failed to retrieve data (status {response.status_code})"}) except Exception as e: results.append({"hash": ipfs_hash, "error": str(e)}) return results # function that handles survey submission # sets up ipfs and blockchain def submission(survey_data): ipfs_hash = upload_json_to_ipfs(survey_data) if ipfs_hash: upload_to_blockchain(ipfs_hash) st.info(f'The IPFS hash is: {ipfs_hash}', icon="ℹ️")\n\n` pythonCode += `total_number_pages = ${totalPages+1}\n`; pythonCode += 'placeholder_buttons = None\n\n'; pythonCode += formatRadioOptions(questions); pythonCode += "\n\n# Function that records radio element changes\n"+ "def radio_change(element, state, key):\n"+ " st.session_state[state] = element.index(st.session_state[key]) # Setting previously selected option\n\n"+ "def multi_change(element, state, key):\n"+ " st.session_state[state] = []\n" + " for selected_option in st.session_state[key]:\n"+ " st.session_state[state].append(selected_option)\n\n"+ "# Function that disables the last button while data is uploaded to IPFS\n"+ "def button_disable():\n"+ " st.session_state['disabled'] = True\n\n"+ "def answer_change(state, key):\n"+ " st.session_state[state] = st.session_state[key]\n\n"; pythonCode += "st.set_page_config(page_title='IPFS-Based Survey',)\n"; pythonCode += `st.title('${surveyTitle}')\n\n`; pythonCode += 'st.markdown("", unsafe_allow_html=True)\n'+ 'st.markdown("", unsafe_allow_html=True)\n\n\n'; // Generate the session state initialization for each question dynamically pythonCode += 'if "current_page" not in st.session_state:\n'; pythonCode += ' st.session_state["current_page"] = 1\n'; for (let i = 1; i <= totalQuestions; i++) { pythonCode += ` st.session_state["Q${i}"] = None\n`; } // Add the final disabled state pythonCode += ' st.session_state["disabled"] = False\n\n'; pythonCode += `# Page 1; Video\nif st.session_state["current_page"] == 1: st.markdown("""

${surveyDescription}

""", unsafe_allow_html=True)\n\n` return pythonCode; } function formatRadioOptions(questions) { let pythonCode = ''; // Initialize Python code for options let questionIndex = 1; questions.forEach(question => { const questionType = question.querySelector('strong').textContent.replace(' Question', ''); if (questionType.toLowerCase() === 'multiple-choice' || questionType.toLowerCase() === 'multiselect' || questionType.toLowerCase() === 'select box') { const options = []; let optionInputs; if (questionType.toLowerCase() === 'multiple-choice') { optionInputs = question.querySelectorAll('.input-group .form-control'); } else if (questionType.toLowerCase() === 'multiselect' || questionType.toLowerCase() === 'select box') { optionInputs = question.querySelectorAll('.input-group2 .form-control-sm'); } optionInputs.forEach(optionInput => { const optionText = optionInput.value.trim(); if (optionText) { options.push(optionText); // Add the non-empty options } }); const optionsVariable = `Q${questionIndex}_radio_options`; if (options.length === 0) { options.push(""); } pythonCode += `${optionsVariable} = ${JSON.stringify(options)}\n`; questionIndex++; } else if (questionType.toLowerCase() === 'slider' || questionType.toLowerCase() === 'text-input') { questionIndex++; } else if (questionType.toLowerCase() === 'likert') { pythonCode += `Q${questionIndex}_radio_options = ["N/A", "Strongly Disagree", "Disagree", "Neutral", "Agree", "Strongly Agree"]\n` questionIndex++; } }); return pythonCode; // Return the formatted Python code for radio options } function formatTextQuestionPython(questionText, question, index) { let pythonCode = ''; // Initialize the Python code for this question pythonCode += ` st.text_area(label = "${questionText}", \n`; pythonCode += ` value= "" if st.session_state["Q${index}"] == None else st.session_state["Q${index}"],\n` pythonCode += ` key = 'Q${index}_text', \n` pythonCode += ` on_change = answer_change,\n` pythonCode += ` args = ( "Q${index}", "Q${index}_text",))\n\n`; return pythonCode; // Return the formatted Python code for this question } function formatMultiselectPython(questionText, question, index) { let pythonCode = ''; // Initialize the Python code for this question // Generate the st.radio function pythonCode += ` st.multiselect(label = "${questionText}", \n`; pythonCode += ` default = None if st.session_state["Q${index}"] == None else st.session_state["Q${index}"], \n`; pythonCode += ` options = Q${index}_radio_options, \n`; pythonCode += ` key = 'Q${index}_multi', \n`; pythonCode += ` on_change = multi_change, \n`; pythonCode += ` args = (Q${index}_radio_options, "Q${index}", "Q${index}_multi",))\n\n`; return pythonCode; // Return the formatted Python code for this question } function formatSliderQuestionsPython(questions, question, index) { let pythonCode = ''; // Initialize Python code const questionType = question.querySelector('strong').textContent.replace(' Question', ''); const questionText = question.querySelector('input[type="text"]').value; const minValue = question.querySelector('input[placeholder="Min Value"]').value || 0; const maxValue = question.querySelector('input[placeholder="Max Value"]').value || 10; const defaultValue = Math.ceil((parseInt(minValue) + parseInt(maxValue)) / 2); pythonCode += ` if st.session_state["Q${index}"] == None:\n` pythonCode += ` st.session_state["Q${index}"] = ${defaultValue}\n` pythonCode += ` st.slider(label="${questionText}",min_value=${minValue},max_value=${maxValue}, value= st.session_state["Q${index}"], key = "Q${index}_slider", on_change = answer_change, args = ("Q${index}", "Q${index}_slider",))\n`; return pythonCode; } function formatSelectBoxPython(questionText, question, index) { let pythonCode = ''; // Initialize the Python code for this question // Generate the st.radio function pythonCode += ` st.selectbox(label = "${questionText}", \n`; pythonCode += ` options = Q${index}_radio_options, \n`; pythonCode += ` index = None if st.session_state["Q${index}"] == None else st.session_state["Q${index}"], \n`; pythonCode += ` key = 'Q${index}_radio', \n`; pythonCode += ` on_change = radio_change, \n`; pythonCode += ` args = (Q${index}_radio_options, "Q${index}", "Q${index}_radio",))\n\n`; return pythonCode; // Return the formatted Python code for this question } function formatVideoPython(questionText) { let pythonCode = ''; // Initialize the Python code for this question pythonCode += ` st.video("${questionText}") \n`; return pythonCode; // Return the formatted Python code for this video } function formatImagePython(questionText) { let pythonCode = ''; // Initialize the Python code for this question pythonCode += ` st.image("${questionText}") \n`; return pythonCode; // Return the formatted Python code for this video } function formatTextPython(questionText) { let pythonCode = ''; // Initialize the Python code for this question pythonCode += ` st.markdown("""

${questionText}

""", unsafe_allow_html=True)\n`; return pythonCode; // Return the formatted Python code for this video } // Function to format a multiple-choice question as Streamlit Python code function formatMultipleChoicePython(questionText, question, index) { let pythonCode = ''; // Initialize the Python code for this question // Generate the st.radio function pythonCode += ` st.radio(label = "${questionText}", \n`; pythonCode += ` options = Q${index}_radio_options, \n`; pythonCode += ` index = None if st.session_state["Q${index}"] == None else st.session_state["Q${index}"], \n`; pythonCode += ` key = 'Q${index}_radio', \n`; pythonCode += ` on_change = radio_change, \n`; pythonCode += ` args = (Q${index}_radio_options, "Q${index}", "Q${index}_radio",))\n\n`; return pythonCode; // Return the formatted Python code for this question } function formatlikert(questionText, question, index) { let pythonCode = ''; // Initialize the Python code for this question // Generate the st.radio function pythonCode += ` st.radio(label = "${questionText}", # Likert\n`; pythonCode += ` options = Q${index}_radio_options, \n`; pythonCode += ` index = None if st.session_state["Q${index}"] == None else st.session_state["Q${index}"], \n`; pythonCode += ` key = 'Q${index}_radio', \n`; pythonCode += ` on_change = radio_change, \n`; pythonCode += ` args = (Q${index}_radio_options, "Q${index}", "Q${index}_radio",))\n\n`; return pythonCode; // Return the formatted Python code for this question } /* IMPORTING CODE (CODE FOR IMPORTING SAVED SURVEY) */ // Function to handle file upload and populate form with pre-made data document.getElementById('import-confirm').addEventListener('click', function () { const fileInput = document.getElementById('import-survey'); const file = fileInput.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { const fileContent = e.target.result; // Parse the Python file content const parsedData = parsePythonSurveyFile(fileContent); // Populate survey form with parsed data populateSurveyForm(parsedData); // Close the modal programmatically after importing const importSurveyModal = bootstrap.Modal.getInstance(document.getElementById('importSurveyModal')); importSurveyModal.hide(); }; reader.readAsText(file); } else { alert("Please select a file to import."); } }); function parsePythonSurveyFile(fileContent) { const surveyData = { title: '', description: '', components: [] }; // Extract title and description const titleMatch = fileContent.match(/st\.title\('(.+?)'\)/); const descriptionMatch = fileContent.match(/st\.markdown\("""

(.*?)<\/p>""",\s*unsafe_allow_html=True\)/); surveyData.title = titleMatch ? titleMatch[1] : ''; surveyData.description = descriptionMatch ? descriptionMatch[1] : ''; // Split content by lines const lines = fileContent.split('\n'); let currentRadio = null; for (let i = 0; i < lines.length; i++) { let line = lines[i].trim(); // Detect new-page components if (line.match(/elif\s+st\.session_state\[\s*["']current_page["']\s*\]\s*==/)) { if (line.match(/elif\s+st\.session_state\[\s*["']current_page["']\s*\]\s*==/)["input"].substring(line.length - 9) !== "Last Page") { surveyData.components.push({ type: 'new-page' }); } } // Detect the start of a radio question (captures label) const radioLabelMatch = line.match(/st\.radio\s*\(\s*label\s*=\s*["](.+?)["](?!\s*,?\s*#)/); if (radioLabelMatch) { currentRadio = { type: 'multiple-choice', label: radioLabelMatch[1], options: [], key: null }; continue; // Move to the next line to find options and key } const likertLabelMatch = line.match(/st\.radio\(.*?label\s*=\s*["](.+?)["]/); if (likertLabelMatch) { const selectSliderQuestion = { type: 'likert', label: likertLabelMatch[1], key: likertLabelMatch[2], }; surveyData.components.push(selectSliderQuestion); continue; } const selectboxLabelMatch = line.match(/st\.selectbox\(.*?label\s*=\s*["](.+?)["]/); if (selectboxLabelMatch) { currentRadio = { type: 'selectbox', label: selectboxLabelMatch[1], options: [], key: null }; continue; // Move to the next line to find options and key } const textAreaMatch = line.match(/st\.text_area\s*\(\s*label\s*=\s*["](.+?)["]/i); if (textAreaMatch) { const textQuestion = { type: 'text-area', label: textAreaMatch[1], key: textAreaMatch[2], }; surveyData.components.push(textQuestion); continue; } const multiSelectLabelMatch = line.match(/st\.multiselect\(.*?label\s*=\s*["](.+?)["]/); if (multiSelectLabelMatch) { currentRadio = { type: 'multiselect', label: multiSelectLabelMatch[1], options: [], key: null }; continue; // Move to the next line to find options and key } const sliderLabelMatch = line.match(/st\.slider\s*\(\s*label\s*=\s*["](.+?)["]\s*,\s*min_value\s*=\s*([0-9]+)\s*,\s*max_value\s*=\s*([0-9]+)/); if (sliderLabelMatch) { const sliderQuestion = { type: 'slider', label: sliderLabelMatch[1], minValue: parseInt(sliderLabelMatch[2], 10), maxValue: parseInt(sliderLabelMatch[3], 10), }; surveyData.components.push(sliderQuestion); continue; } const videoLabelMatch = line.match(/^\s*st\.video\s*\(\s*["](.+?)["]\s*\)/); if (videoLabelMatch) { const videoInput = { type: 'add-video', label: videoLabelMatch[1], }; surveyData.components.push(videoInput); continue; } const imageLabelMatch = line.match(/^\s*st\.image\s*\(\s*["](.+?)["]\s*\)/); if (imageLabelMatch) { const imageInput = { type: 'add-image', label: imageLabelMatch[1], }; surveyData.components.push(imageInput); continue; } const textLabelMatch = line.match(/^\s*st\.markdown\s*\(\s*["']{3}(.+?)<\/p>["']{3}\s*,\s*unsafe_allow_html\s*=\s*True\s*\)/i); if (textLabelMatch) { const textInput = { type: 'add-text', label: textLabelMatch[1], }; surveyData.components.push(textInput); continue; } // If we're inside a radio question, find options and key if (currentRadio) { const optionsMatch = line.match(/options\s*=\s*([\w_]+)_radio_options/); if (optionsMatch) { // Search for the options array in fileContent const optionsVar = optionsMatch[1] + "_radio_options"; const optionsArrayMatch = fileContent.match(new RegExp(`${optionsVar}\\s*=\\s*(\\[.+?\\])`)); currentRadio.options = optionsArrayMatch ? JSON.parse(optionsArrayMatch[1]) : []; } const keyMatch = line.match(/key\s*=\s*["'](.+?)["']/); if (keyMatch) { currentRadio.key = keyMatch[1]; // When we have both options and key, add the question to components surveyData.components.push(currentRadio); currentRadio = null; // Reset for the next radio question } } } return surveyData; } function populateSurveyForm(data) { // Populate title and description if (data.title) { document.getElementById('survey-title').value = data.title; } if (data.description) { document.getElementById('survey-description').value = data.description; } const surveyArea = document.getElementById('survey-area'); surveyArea.innerHTML = ''; // Clear existing form elements // Populate components data.components.forEach((component) => { const componentElement = createQuestionFromPreSavedData(component.type, component); // Append each created component element to the survey area if (componentElement) { surveyArea.appendChild(componentElement); } }); } function createQuestionFromPreSavedData(type, questionData) { const question = document.createElement('div'); question.className = 'question'; question.dataset.type = type; const dragHandle = document.createElement('span'); dragHandle.className = 'drag-handle'; dragHandle.innerHTML = '⋮'; dragHandle.style.cursor = 'move'; const deleteButton = document.createElement('button'); deleteButton.className = 'btn btn-danger btn-sm'; deleteButton.textContent = 'Delete'; deleteButton.addEventListener('click', () => question.remove()); // Create the label element and use the questionData label if available const questionLabel = document.createElement('strong'); questionLabel.textContent = questionData.label || `${type.charAt(0).toUpperCase() + type.slice(1)} Question`; let inputField = null; if (type === 'new-page' || type === 'add-video' || type === 'add-image' || type === 'add-text') { // For the 'new-page' component, only display "New Page" and no input fields if (type === 'new-page') { questionLabel.textContent = 'New Page'; question.appendChild(dragHandle); // Add the custom handle question.appendChild(questionLabel); question.appendChild(deleteButton); // Append the delete button } else if (type === 'add-video') { questionLabel.textContent = 'Add Video'; const questionText = document.createElement('input'); questionText.type = 'text'; questionText.placeholder = 'Enter your video link here'; questionText.className = 'form-control mb-2'; questionText.value = questionData.label || ''; // Set the label as the input value question.appendChild(dragHandle); // Add the custom handle question.appendChild(questionLabel); question.appendChild(deleteButton); // Append the delete button question.appendChild(questionText); } else if (type === 'add-image') { questionLabel.textContent = 'Add Image'; const questionText = document.createElement('input'); questionText.type = 'text'; questionText.placeholder = 'Enter your image link here'; questionText.className = 'form-control mb-2'; questionText.value = questionData.label || ''; // Set the label as the input value question.appendChild(dragHandle); // Add the custom handle question.appendChild(questionLabel); question.appendChild(deleteButton); // Append the delete button question.appendChild(questionText); } else if (type === 'add-text') { questionLabel.textContent = 'Add Text'; const questionText = document.createElement('input'); questionText.type = 'text'; questionText.placeholder = 'Enter the text you want to appear here'; questionText.className = 'form-control mb-2'; questionText.value = questionData.label || ''; // Set the label as the input value question.appendChild(dragHandle); // Add the custom handle question.appendChild(questionLabel); question.appendChild(deleteButton); // Append the delete button question.appendChild(questionText); } } else { const questionText = document.createElement('input'); questionText.type = 'text'; questionText.placeholder = 'Enter your question here'; questionText.className = 'form-control mb-2'; questionText.value = questionData.label || ''; // Set the label as the input value if (type === 'multiple-choice') { questionLabel.textContent = 'Multiple-choice Question'; } else if (type === 'multiselect') { questionLabel.textContent = 'Multiselect Question'; } else if (type === 'slider') { questionLabel.textContent = 'Slider Question'; } else if (type === 'text-area') { questionLabel.textContent = 'Text-Input Question'; } else if (type === 'selectbox') { questionLabel.textContent = 'Select Box Question'; } else if (type === 'likert') { questionLabel.textContent = 'Likert Question'; } question.appendChild(dragHandle); question.appendChild(questionLabel); question.appendChild(deleteButton); question.appendChild(questionText); if (type === 'multiple-choice') { inputField = createMultipleChoiceOptionsFromData(questionData.options); } else if (type === 'multiselect') { inputField = createMultiselectOptionsFromData(questionData.options); } else if (type === 'slider') { inputField = createSliderOptionsFromData(questionData); } else if (type === 'selectbox') { inputField = createSelectBoxOptionsFromData(questionData.options); } } if (inputField) { question.appendChild(inputField); } return question; } function createMultipleChoiceOptionsFromData(options) { const inputField = document.createElement('div'); options.forEach((optionText, index) => { const optionContainer = document.createElement('div'); optionContainer.className = 'input-group mb-1'; const optionInput = document.createElement('input'); optionInput.type = 'text'; optionInput.placeholder = `Option ${index + 1}`; optionInput.className = 'form-control'; optionInput.value = optionText; const deleteOptionButton = document.createElement('button'); deleteOptionButton.className = 'btn btn-danger btn-sm'; deleteOptionButton.textContent = 'X'; deleteOptionButton.addEventListener('click', () => optionContainer.remove()); const inputGroupAppend = document.createElement('div'); inputGroupAppend.className = 'input-group-append'; inputGroupAppend.appendChild(deleteOptionButton); optionContainer.appendChild(optionInput); optionContainer.appendChild(inputGroupAppend); inputField.appendChild(optionContainer); }); const addOptionButton = document.createElement('button'); addOptionButton.className = 'btn btn-secondary btn-sm mb-2'; addOptionButton.textContent = 'Add Option'; addOptionButton.addEventListener('click', () => { const optionCount = inputField.querySelectorAll('.input-group').length + 1; inputField.insertBefore(createOptionInput(`Option ${optionCount}`), addOptionButton); }); inputField.appendChild(addOptionButton); return inputField; } function createOptionInput(placeholder) { const optionContainer = document.createElement('div'); optionContainer.className = 'input-group mb-1'; const optionInput = document.createElement('input'); optionInput.type = 'text'; optionInput.placeholder = placeholder; optionInput.className = 'form-control'; const deleteOptionButton = document.createElement('button'); deleteOptionButton.className = 'btn btn-danger btn-sm'; deleteOptionButton.textContent = 'X'; deleteOptionButton.addEventListener('click', () => optionContainer.remove()); const inputGroupAppend = document.createElement('div'); inputGroupAppend.className = 'input-group-append'; inputGroupAppend.appendChild(deleteOptionButton); optionContainer.appendChild(optionInput); optionContainer.appendChild(inputGroupAppend); return optionContainer; } function createMultiselectOptionsFromData(options) { const inputField = document.createElement('div'); options.forEach((optionText, index) => { const optionContainer = document.createElement('div'); optionContainer.className = 'input-group2 mb-1'; const optionInput = document.createElement('input'); optionInput.type = 'text'; optionInput.placeholder = `Option ${index + 1}`; optionInput.className = 'form-control-sm'; optionInput.value = optionText; const deleteOptionButton = document.createElement('button'); deleteOptionButton.className = 'btn btn-danger btn-sm'; deleteOptionButton.textContent = 'X'; deleteOptionButton.addEventListener('click', () => optionContainer.remove()); const inputGroupAppend = document.createElement('div'); inputGroupAppend.className = 'input-group-append'; inputGroupAppend.appendChild(deleteOptionButton); optionContainer.appendChild(optionInput); optionContainer.appendChild(inputGroupAppend); inputField.appendChild(optionContainer); }); function createMultiselectOption(placeholder) { const multiselectContainer = document.createElement('div'); multiselectContainer.className = 'input-group2 mb-1'; const labelInput = document.createElement('input'); labelInput.type = 'text'; labelInput.className = 'form-control-sm'; labelInput.placeholder = placeholder; const deleteOptionButton = document.createElement('button'); deleteOptionButton.className = 'btn btn-danger btn-sm'; deleteOptionButton.textContent = 'X'; deleteOptionButton.addEventListener('click', () => multiselectContainer.remove()); multiselectContainer.appendChild(labelInput); multiselectContainer.appendChild(deleteOptionButton); return multiselectContainer; } const addmultiselectButton = document.createElement('button'); addmultiselectButton.className = 'btn btn-secondary btn-sm mb-2'; addmultiselectButton.textContent = 'Add Option'; addmultiselectButton.addEventListener('click', () => { const optionCount = inputField.querySelectorAll('.input-group2').length + 1; inputField.insertBefore(createMultiselectOption(`Option ${optionCount}`), addmultiselectButton); }); inputField.appendChild(addmultiselectButton); return inputField; } function createSliderOptionsFromData(questionData) { const inputField = document.createElement('div'); // Create a container for the slider labels const sliderLabels = document.createElement('div'); sliderLabels.className = 'd-flex justify-content-between mb-2'; inputField.appendChild(sliderLabels); // Create input fields for the min and max values of the slider with labels const minMaxContainer = document.createElement('div'); minMaxContainer.className = 'd-flex justify-content-between mb-2'; // Min Value Label and Input const minValueWrapper = document.createElement('div'); const minValueLabel = document.createElement('label'); minValueLabel.innerText = 'Min Value'; minValueLabel.className = 'form-label'; const minValueInput = document.createElement('input'); minValueInput.type = 'number'; minValueInput.className = 'form-control'; minValueInput.placeholder = 'Min Value'; minValueInput.value = questionData.minValue || 0; // Use min value from questionData or default to 0 minValueWrapper.appendChild(minValueLabel); minValueWrapper.appendChild(minValueInput); // Max Value Label and Input const maxValueWrapper = document.createElement('div'); const maxValueLabel = document.createElement('label'); maxValueLabel.innerText = 'Max Value'; maxValueLabel.className = 'form-label'; const maxValueInput = document.createElement('input'); maxValueInput.type = 'number'; maxValueInput.className = 'form-control'; maxValueInput.placeholder = 'Max Value'; maxValueInput.value = questionData.maxValue || 10; // Use max value from questionData or default to 10 maxValueWrapper.appendChild(maxValueLabel); maxValueWrapper.appendChild(maxValueInput); minMaxContainer.appendChild(minValueWrapper); minMaxContainer.appendChild(maxValueWrapper); // Create a warning message for invalid input const warningMessage = document.createElement('small'); warningMessage.style.color = 'red'; warningMessage.style.display = 'none'; warningMessage.innerText = 'Max value must be greater than Min value'; inputField.appendChild(minMaxContainer); inputField.appendChild(warningMessage); // Add event listeners to check max > min validation function validateMinMax() { const min = parseFloat(minValueInput.value); const max = parseFloat(maxValueInput.value); if (max <= min) { warningMessage.style.display = 'block'; // Show the warning message } else { warningMessage.style.display = 'none'; // Hide the warning message } } // Listen for changes in the min and max input fields minValueInput.addEventListener('input', validateMinMax); maxValueInput.addEventListener('input', validateMinMax); return inputField; } function createSelectBoxOptionsFromData(options) { const inputField = document.createElement('div'); options.forEach((optionText, index) => { const optionContainer = document.createElement('div'); optionContainer.className = 'input-group2 mb-1'; const optionInput = document.createElement('input'); optionInput.type = 'text'; optionInput.placeholder = `Option ${index + 1}`; optionInput.className = 'form-control-sm'; optionInput.value = optionText; const deleteOptionButton = document.createElement('button'); deleteOptionButton.className = 'btn btn-danger btn-sm'; deleteOptionButton.textContent = 'X'; deleteOptionButton.addEventListener('click', () => optionContainer.remove()); const inputGroupAppend = document.createElement('div'); inputGroupAppend.className = 'input-group-append'; inputGroupAppend.appendChild(deleteOptionButton); optionContainer.appendChild(optionInput); optionContainer.appendChild(inputGroupAppend); inputField.appendChild(optionContainer); }); function createSelectBoxOption(placeholder) { const multiselectContainer = document.createElement('div'); multiselectContainer.className = 'input-group2 mb-1'; const labelInput = document.createElement('input'); labelInput.type = 'text'; labelInput.className = 'form-control-sm'; labelInput.placeholder = placeholder; const deleteOptionButton = document.createElement('button'); deleteOptionButton.className = 'btn btn-danger btn-sm'; deleteOptionButton.textContent = 'X'; deleteOptionButton.addEventListener('click', () => multiselectContainer.remove()); multiselectContainer.appendChild(labelInput); multiselectContainer.appendChild(deleteOptionButton); return multiselectContainer; } const addmultiselectButton = document.createElement('button'); addmultiselectButton.className = 'btn btn-secondary btn-sm mb-2'; addmultiselectButton.textContent = 'Add Option'; addmultiselectButton.addEventListener('click', () => { const optionCount = inputField.querySelectorAll('.input-group2').length + 1; inputField.insertBefore(createSelectBoxOption(`Option ${optionCount}`), addmultiselectButton); }); inputField.appendChild(addmultiselectButton); return inputField; }