SurveyBuilder / dragdrop.js
DragandDropGroup's picture
Upload dragdrop.js
6676ba4 verified
/*
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("""<style> div[class*="stRadio"] > label > div[data-testid="stMarkdownContainer"] > p {font-size: 18px;}</style> <br><br>""", unsafe_allow_html=True)\n\n\n`;
} else if (questionType.toLowerCase() === 'text-input') {
pythonCode += formatTextQuestionPython(questionText, question, questionIndex);
pythonCode += ` st.markdown("""<style> div[class*="stText"] > label > div[data-testid="stMarkdownContainer"] > p {font-size: 18px;}</style> <br><br>""", unsafe_allow_html=True)\n\n\n`;
} else if (questionType.toLowerCase() === 'multiselect') {
pythonCode += formatMultiselectPython(questionText, question, questionIndex);
pythonCode += ` st.markdown("""<style> div[class*="stMulti"] > label > div[data-testid="stMarkdownContainer"] > p {font-size: 18px;}</style> <br><br>""", unsafe_allow_html=True)\n\n\n`;
} else if (questionType.toLowerCase() === 'slider') {
pythonCode += formatSliderQuestionsPython(questionText, question, questionIndex);
pythonCode += ` st.markdown("""<style> div[class*="stSlider"] > label > div[data-testid="stMarkdownContainer"] > p {font-size: 18px;}</style> <br><br>""", unsafe_allow_html=True)\n\n\n`;
} else if (questionType.toLowerCase() === 'select box') {
pythonCode += formatSelectBoxPython(questionText, question, questionIndex);
pythonCode += ` st.markdown("""<style> div[class*="stSelectbox"] > label > div[data-testid="stMarkdownContainer"] > p {font-size: 18px;}</style> <br><br>""", unsafe_allow_html=True)\n\n\n`;
} else if (questionType.toLowerCase() === 'likert') {
pythonCode += formatlikert(questionText, question, questionIndex);
pythonCode += ` st.markdown("""<style> div[class*="stRadio"] > label > div[data-testid="stMarkdownContainer"] > p {font-size: 18px;}</style> <br><br>""", 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('<p class="big-font">Thank you for participating! <br> Click on the button below to submit your answers. </p>', 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("<style>.row-widget.stButton {text-align: center;}</style>", unsafe_allow_html=True)\n'+
'st.markdown("<style>.big-font {font-size:24px;}</style>", 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("""<p style='font-size:18px ;'>${surveyDescription}</p>""", 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("""<p style='font-size:18px;'>${questionText}</p>""", 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 style='font-size:18px ;'>(.*?)<\/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\s+style=['"]font-size:\s*18px;['"]>(.+?)<\/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;
}