首页 > 解决方案 > 使用JS在Thymeleaf中生成输入字段的id和name属性无效

问题描述

我遇到了一个非常奇怪的问题,我找不到原因。简而言之,当我尝试在 update.html 中的应用程序中更新一些先前给出的答案时,某些东西会为我的一个输入字段生成随机 id 和随机名称值,因此这些特定数据不会发送到后端。

这是我的 update.html 的控制器方法

 @GetMapping("/update/{questionFormId}/{appUserId}")
 public String updateAnswerFormCreatedByUser(@PathVariable long questionFormId, @PathVariable long appUserId, Model model) {
   log.info("GET answer-form/update/" + questionFormId + "/" + appUserId + " started");
   try {
     model.addAttribute("answerForm", answerFormService.createAnswerFormToUpdate(questionFormId, appUserId));
     log.info("GET answer-form/update/" + questionFormId + "/" + appUserId + " finished");
     return "answerform/update";

这是我发送到 HTML 的对象的类

public class CreateAnswerFormDTO {
  private long questionFormId;
  private long answerFormId;
  private long appUserId;
  private List<Question> questions = new ArrayList<>();
  private List<Answer> answers = new ArrayList<>();

  public CreateAnswerFormDTO() {
  }
// with other constructors and getters/setters

问题类

// I have four different types of questions (text, checkbox, radio and scale(radio but with numbers) 
//and I store them in the same table and they have some different class variables as well)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "Question_Type")
@Where(clause="deleted=0")
public class Question implements Comparable<Question> {

 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private long id;
 @NotNull
 private String questionText;
 private int listPosition;
 private boolean deleted;

 @Transient
 private List<String> answersNotFilledOutByUser = new ArrayList<>();

 @ManyToOne(cascade = {CascadeType.MERGE})
 @JsonBackReference(value = "questionsQuestionForm")
 private QuestionForm questionForm;

 @OneToMany(mappedBy = "question", cascade = {CascadeType.MERGE})
 @JsonManagedReference("answersQuestion")
 private List<Answer> answers = new ArrayList<>();
// with constructors, getters and setters

我的答案课

public class Answer {
// I store the concrete answertexts provided by the users in the ActualAnswerText class, 
// because f.e. text questions or checkbox questions can have multiple answer texts"
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private long id;

  private boolean deleted;

  @OneToMany(mappedBy = "answer", cascade = {CascadeType.MERGE, CascadeType.PERSIST})
  @JsonManagedReference(value = "answersActualAnswerTexts") 
  private List<ActualAnswerText> actualAnswerTexts = new ArrayList<>();

  @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
  @JsonBackReference(value = "answersAnswerform")
  private AnswerForm answerForm;

  @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
  @JsonBackReference("answersQuestion")
  private Question question;
// with constructors, getters and setters

我的更新html

<head>
  <meta charset="UTF-8">
  <title>Update Answerform</title>
  <link th:replace="fragments/navbar.html :: bootstrap-link">
  <link th:replace="fragments/navbar.html :: css">
</head>
<body>
<script th:src="@{/js/renderanswerform.js}" type="text/javascript"></script>
<header th:replace="fragments/navbar.html :: navbar"></header>
<div th:replace="fragments/messages.html :: error"></div>
<h1>Update your answerform</h1> 
<form id="answerform" method="post"
      th:action="@{|/answer-form/update/${answerForm.answerFormId}/${answerForm.appUserId}|}">
  <tr th:each="question, iterStat : ${answerForm.questions}">
    <p th:text="${question.questionText}"></p>  
<!--Rendering the question's text-->
    <div>
      <th:block th:switch="${question.getDiscriminatorValue()}"> 
<!--checking what questiontype the current question belongs to-->

        <div th:case="'CheckBoxQuestion'">
                        <span th:each="questionTextPossibility, list: ${question.questionTextPossibilities}">
                            <label th:text="${questionTextPossibility.answerText}"></label>  <!--Displaying the possible answertext value-->
                          <!-- I use th:field to prefill-->
                          <!-- I use th:name to make it available on the backend-->
                          <!-- I use th:value to set what value will be sent to the backend when clicking on this checkbox-->
                            <input
                                th:field="${answerForm.answers[__${iterStat.index}__].actualAnswerTexts[__${list.index}__].answerText}"
                                th:name="answers[__${iterStat.index}__].actualAnswerTexts[__${list.index}__].answerText"
                                th:value="${questionTextPossibility.answerText}"
                                type="checkbox">
                        </span>
        </div>
        <div th:case="'RadioButtonQuestion'">
                        <span th:each="questionTextPossibility, list: ${question.questionTextPossibilities}">
                            <label th:text="${questionTextPossibility.answerText}"></label>

                   <input th:field="${answerForm.answers[__${iterStat.index}__].actualAnswerTexts[0].answerText}"
                          th:name="answers[__${iterStat.index}__].actualAnswerTexts[0].answerText"
                          th:value="${questionTextPossibility.answerText}"
                          type="radio">
                        </span>
        </div>

        <div th:case="'ScaleQuestion'">
          <th:block th:each="i: ${#numbers.sequence(1, question.scale)}">
 <!-- Scale question generates radio button with numbers until the scale attribute of ScaleQuestion class-->
            <label th:text="${i}"></label>
            <input th:field="${answerForm.questions[__${iterStat.index}__].answers[0].actualAnswerTexts[0].answerText}"
                   th:name="answers[__${iterStat.index}__].actualAnswerTexts[0].answerText"
                   th:value="${i}"
                   type="radio">
          </th:block>
        </div>

        <div th:case="'TextQuestion'" th:id="|textquestioncreate[__${iterStat.index}__]|">
          <!--With TextQuestion users can provided more than one answer.
 f.e What are the best movies? They can generate more than one input fields to provide answers -->
                 <span th:id="|answers[__${iterStat.index}__]|">
                   <span
                       th:each="actualAnswerText, list: ${answerForm.answers[__${iterStat.index}__].actualAnswerTexts}">

                     <input
                         th:field="${answerForm.answers[__${iterStat.index}__].actualAnswerTexts[__${list.index}__].answerText}"
                         th:name="answers[__${iterStat.index}__].actualAnswerTexts[__${list.index}__].answerText"
                         type="text">
                    </span>
                   </span>

          <script th:inline="javascript"> 
 <!-- This js code creates the buttons that enable the adding of new input fields,
 finishing given question and re-enabling modification-->
              /*<![CDATA[*/
              renderQuestionText(/*[[${iterStat.index}]]*/)
              /*]]>*/
              /*<![CDATA[*/
              resetForCreatingTextQuestion()
              /*]]>*/
          </script>
        </div>
      </th:block>
    </div>
  </tr>
  </table>
</form>

最后,我的 renderanswerform.js 文件

// I haven't listed all the functions because some are just for creating
// finish, submit, reset and re-enable buttons
var actualAnswerIndexForTextQuestion = 0;
var buttonIndexToEnable = 0;
var currentTextAnswerIndex = 0;
var numberOfInputFieldsCreated = 0;
var hasInputfieldsCreated = false;

window.onload = function WindowLoad(event) {
    createValidateResetAndSubmitButtons();
}

function renderQuestionText(index) {
    if (!hasInputfieldsCreated) {
        let container = document.getElementById("textquestioncreate[" + index + "]");
        container.appendChild(createTextQuestionInput(index));
        hasInputfieldsCreated = true;
    }
}

function renderQuestionTextForCreate(index) {
    let container = document.getElementById("textquestioncreate[" + index + "]");
    container.appendChild(createTextQuestionInput(index));
    hasInputfieldsCreated = true;
}


function createTextQuestionInput(index) {
    let container = document.getElementById("answers[" + index + "]");
    let anotherInputButton = createAnotherInputButton(index);
    anotherInputButton.disabled = true;
    anotherInputButton.id = "textanswerbutton" + currentTextAnswerIndex;
    let finishButton = createFinishButtonForTextAnswer(currentTextAnswerIndex);

    let reEnableButton = createReenableButton(currentTextAnswerIndex);

    container.appendChild(anotherInputButton);
    container.appendChild(finishButton);
    container.appendChild(reEnableButton);
    enableDisableFinishAndAddAnotherTextFieldButtons();
    currentTextAnswerIndex++;
    return container;
}

function createAnotherInputButton(index) {
    let anotherInputButton = document.createElement("BUTTON");
    anotherInputButton.id = "anotherinputbutton" + index;
    anotherInputButton.textContent = "Create another input field";
    anotherInputButton.className = "buttonsToDisableEnable";
    anotherInputButton.type = "button";
    anotherInputButton.addEventListener("click", () => createAnotherInputField(index));
    return anotherInputButton;
}

function createAnotherInputField(index) {
    let elementId = "answers[" + index + "]";
    let container = document.getElementById(elementId);
    let numberOfInputFieldsInContainer = container.getElementsByTagName('INPUT').length
    let input = document.createElement("INPUT");
    numberOfInputFieldsCreated++;
    input.id = "textanswer" + currentTextAnswerIndex;
    actualAnswerIndexForTextQuestion++;
    input.name = "answers" + "[" + index + "].actualAnswerTexts[" + numberOfInputFieldsInContainer + "].answerText";
    container.insertBefore(input, document.getElementById("textanswerbutton" + buttonIndexToEnable));
}

在 ScaleQuestion 输入中没有正确的名称属性,而是在浏览器中检查它时看到以下内容。我在代码中的任何地方都没有提供任何 id,并且 name 属性的开头不应包含“question0.answers0”,只能包含 answers[0]。启动应用程序时 HTML 中的 ScaleQuestion(不正确)

<div>
          
            <label>1</label>
            <input name="questions[2].answers[0].actualAnswerTexts[0].answerText" value="1" type="radio" id="questions2.answers0.actualAnswerTexts0.answerText1">
          
            <label>2</label>
            <input name="questions[2].answers[0].actualAnswerTexts[0].answerText" value="2" type="radio" id="questions2.answers0.actualAnswerTexts0.answerText2">
          
            <label>3</label>
            <input name="questions[2].answers[0].actualAnswerTexts[0].answerText" value="3" type="radio" id="questions2.answers0.actualAnswerTexts0.answerText3">
          
            <label>4</label>
            <input name="questions[2].answers[0].actualAnswerTexts[0].answerText" value="4" type="radio" id="questions2.answers0.actualAnswerTexts0.answerText4">
          
            <label>5</label>
            <input name="questions[2].answers[0].actualAnswerTexts[0].answerText" value="5" type="radio" id="questions2.answers0.actualAnswerTexts0.answerText5">
          
            <label>6</label>
            <input name="questions[2].answers[0].actualAnswerTexts[0].answerText" value="6" type="radio" id="questions2.answers0.actualAnswerTexts0.answerText6">
          
            <label>7</label>
            <input name="questions[2].answers[0].actualAnswerTexts[0].answerText" value="7" type="radio" id="questions2.answers0.actualAnswerTexts0.answerText7">
          
            <label>8</label>
            <input name="questions[2].answers[0].actualAnswerTexts[0].answerText" value="8" type="radio" id="questions2.answers0.actualAnswerTexts0.answerText8">
          
        </div>

单选按钮问题(正确):

<span>
                           <label>radio1</label>

                  <input name="answers[0].actualAnswerTexts[0].answerText" value="radio1" type="radio" id="answers0.actualAnswerTexts0.answerText1">
                       </span>

到目前为止我已经尝试过:

我的猜测是 js 代码的某些先前状态会以某种方式干扰(因为在此之前所有输入字段都是在 js 中生成的),但我看不到如何或在哪里。

提前感谢您的帮助!

标签: javahtmlspringthymeleaf

解决方案


推荐阅读