Image I’m submitting the pollThe application is used for creating polls, polls can have many questions and questions many options, So the issue is that when I add e new survey(poll) in default we have 4 fields the poll’s name, first question and 2 options, i filled them up and then pressed the button add option and added 3 more options so it became 5 options for the first question, then i pressed the button add question and default we have 3 fields the question desc. and 2 options I filled them and then pressed button add option and here instead of adding an option to the second question it saves the second question with 2 options and creates the third question with question desc and 2 options null and 3rd option as it should in the second question.
The method is
@PostMapping(“/poll/create”)
public String createPoll(@ModelAttribute PollData pollData, Model model)
How it Is (WITH ERROR)
Question 1: Question 1
Option 1: option 1
Option 2: option 2
Option 3: option 3
Option 4: option 4
Option 5: option 5
Question 2: Second question
Option 1: option 2.1
Option 2: option 2.2
Question 3: null
Option 1: null
Option 2: null
Option 3: option 2.3
Option 4: option 2.4
Option 5: option 2.5
How should be
Question 1: Question 1
Option 1: option 1
Option 2: option 2
Option 3: option 3
Option 4: option 4
Option 5: option 5
Question 2: Second question
Option 1: option 2.1
Option 2: option 2.2
Option 3: option 2.3
Option 4: option 2.4
Option 5: option 2.5
package finki.ukim.mk.surveyKing.controller;
import finki.ukim.mk.surveyKing.model.*;
import finki.ukim.mk.surveyKing.service.OptionService;
import finki.ukim.mk.surveyKing.service.PollService;
import finki.ukim.mk.surveyKing.service.QuestionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@Controller
public class PollController {
@Autowired
private PollService pollService;
@Autowired
private QuestionService questionService;
@Autowired
private OptionService optionService;
@GetMapping("/")
public String homePage(Model model) {
model.addAttribute("polls", pollService.getAllPols());
return "index";
}
@GetMapping("/poll/{id}")
public String viewPoll(@PathVariable int id, Model model) {
Poll poll = pollService.getPollById(id);
model.addAttribute("poll", poll);
model.addAttribute("questions", poll.getQuestions());
return "poll";
}
@GetMapping("/poll/create")
public String createPollForm(Model model) {
model.addAttribute("poll", new Poll());
return "create_poll";
}
@PostMapping("/poll/create")
public String createPoll(@ModelAttribute PollData pollData, Model model) {
// Log the pollData structure to check what is being submitted
System.out.println("Poll Name: " + pollData.getName());
for (int i = 0; i < pollData.getQuestions().size(); i++) {
QuestionData questionData = pollData.getQuestions().get(i);
System.out.println("Question " + (i + 1) + ": " + questionData.getText());
for (int j = 0; j < questionData.getOptions().size(); j++) {
OptionData optionData = questionData.getOptions().get(j);
System.out.println("Option " + (j + 1) + ": " + optionData.getDescription());
}
}
// Proceed with validation
if (pollData.getName() == null || pollData.getName().isEmpty()) {
model.addAttribute("error", "Poll name is required");
return "create_poll";
}
if (pollData.getQuestions() == null || pollData.getQuestions().isEmpty()) {
model.addAttribute("error", "At least one question is required");
return "create_poll";
}
for (QuestionData questionData : pollData.getQuestions()) {
if (questionData.getText() == null || questionData.getText().isEmpty()) {
model.addAttribute("error", "All questions must have text");
return "create_poll";
}
if (questionData.getOptions() == null || questionData.getOptions().isEmpty()) {
model.addAttribute("error", "Each question must have at least one option");
return "create_poll";
}
for (OptionData optionData : questionData.getOptions()) {
if (optionData.getDescription() == null || optionData.getDescription().isEmpty()) {
model.addAttribute("error", "All options must have descriptions");
return "create_poll";
}
}
}
// If everything is fine, create the poll
Poll poll = new Poll();
poll.setName(pollData.getName());
List<Question> questionList = new ArrayList<>();
for (QuestionData questionData : pollData.getQuestions()) {
Question question = new Question();
question.setText(questionData.getText());
question.setPoll(poll);
List<Option> optionList = new ArrayList<>();
for (OptionData optionData : questionData.getOptions()) {
Option option = new Option();
option.setDescription(optionData.getDescription());
option.setQuestion(question);
option.setPoll(poll);
optionList.add(option);
}
question.setOptions(optionList);
questionList.add(question);
}
poll.setQuestions(questionList);
pollService.createPoll(poll);
return "redirect:/";
}
@PostMapping("/vote")
public String createVote(@RequestParam("selectedOptions") List<Integer> selectedOptionIds,
@RequestParam("pollId") int pollId) {
for (Integer optionId : selectedOptionIds) {
optionService.incrementVote(optionId);
}
return "redirect:/";
}
@GetMapping("/poll/{id}/results")
public String pollResults(@PathVariable int id, Model model) {
Poll poll = pollService.getPollById(id);
model.addAttribute("poll", poll);
return "results";
}
@GetMapping("/poll/edit/{id}")
public String editPollForm(@PathVariable int id, Model model) {
Poll poll = pollService.getPollById(id);
model.addAttribute("poll", poll);
return "edit_poll"; // Create an "edit_poll.html" Thymeleaf template
}
@PostMapping("/poll/edit/{id}")
public String editPoll(@PathVariable int id, @ModelAttribute PollData pollData) {
Poll poll = pollService.getPollById(id);
pollService.updatePoll(poll, pollData);
return "redirect:/";
}
@PostMapping("/poll/delete/{id}")
public String deletePoll(@PathVariable int id) {
pollService.deletePoll(id);
return "redirect:/";
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Create Poll</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div th:if="${error}" class="alert alert-danger">
<p th:text="${error}"></p>
</div>
<h1 class="mt-4 mb-3">Create a New Poll</h1>
<form th:action="@{/poll/create}" method="post">
<div class="form-group">
<label for="name">Poll Name:</label>
<input type="text" id="name" name="name" class="form-control" required>
</div>
<div id="questionsContainer">
<div class="form-group question-block">
<label>Question:</label>
<input type="text" name="questions[0].text" class="form-control mb-2" placeholder="Question Text" required>
<div class="optionsContainer">
<div class="form-group option-block">
<input type="text" name="questions[0].options[0].description" class="form-control mb-2" placeholder="Option 1" required>
</div>
<div class="form-group option-block">
<input type="text" name="questions[0].options[1].description" class="form-control mb-2" placeholder="Option 2" required>
</div>
</div>
<button type="button" class="btn btn-secondary add-option-btn mb-2">Add Option</button>
</div>
</div>
<button type="button" class="btn btn-primary mt-2" id="addQuestionBtn">Add Question</button>
<button type="submit" class="btn btn-success mt-2">Create Poll</button>
</form>
</div>
<script>
let questionIndex = 1; // Tracks the current number of questions
let optionIndices = { 0: 2 }; // Tracks the current number of options for each question
// Function to add a new question block
function addQuestionBlock() {
const questionsContainer = document.getElementById('questionsContainer');
const newQuestionBlock = document.createElement('div');
newQuestionBlock.className = 'question-block form-group';
newQuestionBlock.setAttribute('data-question-index', questionIndex); // Set data attribute for easier access
newQuestionBlock.innerHTML = `
<label>Question:</label>
<input type="text" name="questions[${questionIndex}].text" class="form-control mb-2" placeholder="Question Text" required>
<div class="optionsContainer">
<div class="form-group option-block">
<input type="text" name="questions[${questionIndex}].options[0].description" class="form-control mb-2" placeholder="Option 1" required>
</div>
<div class="form-group option-block">
<input type="text" name="questions[${questionIndex}].options[1].description" class="form-control mb-2" placeholder="Option 2" required>
</div>
</div>
<button type="button" class="btn btn-secondary add-option-btn mb-2">Add Option</button>
`;
questionsContainer.appendChild(newQuestionBlock);
// Initialize the option index for this question
optionIndices[questionIndex] = 2;
// Add event listener for adding options in this question block
newQuestionBlock.querySelector('.add-option-btn').addEventListener('click', function () {
addOption(newQuestionBlock.querySelector('.optionsContainer'), questionIndex);
});
questionIndex++; // Increment the question index
}
// Function to add a new option within a specific question block
function addOption(optionsContainer, questionIdx) {
// Ensure the option index for this question is initialized
if (typeof optionIndices[questionIdx] === 'undefined') {
optionIndices[questionIdx] = optionsContainer.querySelectorAll('.option-block').length;
}
const optionCount = optionIndices[questionIdx];
// Create a new input for the option without replacing the entire innerHTML
const newOptionInput = document.createElement('div');
newOptionInput.className = 'form-group option-block';
newOptionInput.innerHTML = `
<input type="text" name="questions[${questionIdx}].options[${optionCount}].description" class="form-control mb-2" placeholder="Option ${optionCount + 1}" required>
`;
optionsContainer.appendChild(newOptionInput);
// Increment the option index for this question
optionIndices[questionIdx]++;
}
// Add event listener for the initial add-question button
document.getElementById('addQuestionBtn').addEventListener('click', addQuestionBlock);
// Add event listener for the initial add-option button in the first question
document.querySelector('.add-option-btn').addEventListener('click', function () {
const firstOptionsContainer = document.querySelector('.optionsContainer');
addOption(firstOptionsContainer, 0); // 0 is the index of the first question
});
// Ensure form submission only happens when all data is correctly in place
document.querySelector('form').addEventListener('submit', function (event) {
let isValid = true;
for (let i = 0; i < questionIndex; i++) {
const questionText = document.querySelector(`input[name="questions[${i}].text"]`);
if (!questionText || questionText.value.trim() === '') {
alert(`Question ${i + 1} must have text.`);
isValid = false;
break;
}
const optionsCount = optionIndices[i];
if (optionsCount === undefined || optionsCount < 1) {
alert(`Question ${i + 1} must have at least one option.`);
isValid = false;
break;
}
for (let j = 0; j < optionsCount; j++) {
const optionText = document.querySelector(`input[name="questions[${i}].options[${j}].description"]`);
if (!optionText || optionText.value.trim() === '') {
alert(`Option ${j + 1} in Question ${i + 1} must have text.`);
isValid = false;
break;
}
}
if (!isValid) break;
}
if (!isValid) {
event.preventDefault();
} else {
console.log("Form is valid, submitting...");
}
});
</script>
</body>
</html>
The error you can see in the explanation above.