Struts2 - 如何在验证错误后使用提交的值重新填充具有动态生成的字段名(通过表达式)的表单
Struts2 - How to repopulate form having dynamically generated fieldnames (via expression) with submitted values after validation error
我们有一个 Struts2 应用程序,我们正在构建调查功能,应用程序用户可以使用该应用程序通过添加不同的问题来创建调查。
这些问题由问题文本和用于获取响应的 html 控件组成。
到目前为止支持的 html 控件是 single 和 multi-select list, 文本字段、文本区域、复选框和单选框.
因此调查表呈现显示用户添加的所有问题,其中每个问题都显示其问题文本,后跟 html 字段控件 select 为该问题编辑。
基本上,它是一个动态表单,其中表单字段名称是动态生成的,因为所有调查都不同,因此 Action class 支持调查表单字段中没有属性。
我们正在生成表单字段名称,使用前缀 question_
附加问题的数据库 ID,以唯一地表示每个问题响应输入字段。这是我们 JSP 页面的一个片段,可以清楚地说明这一点。
<s:form id="surveyForm" action="survey/submitFeedback">
<s:iterator value="surveyQuestions">
<p class="form-group <s:if test="%{fieldErrors.get('question_' + surveyQuestionId).size() > 0}">has-error</s:if>" >
<label class="control-label" >
<s:property value="questionText"/>
</label>
<s:if test="required" ><span style='color:red'>*</span></s:if>
<br>
<s:if test="surveyQuestionType.type == @com.reach150.enumeration.SurveyQuestionTypeEnum@OPENENDED_TEXTFIELD" >
<s:textfield name="question_%{surveyQuestionId}" cssClass="form-control" maxlength="charactersLimit" />
</s:if>
<s:elseif test="surveyQuestionType.type == @com.reach150.enumeration.SurveyQuestionTypeEnum@OPENENDED_TEXTAREA" >
<s:textarea name="question_%{surveyQuestionId}" style="height: 150px; width: 400px;" cssClass="form-control" maxlength="charactersLimit" />
</s:elseif>
<s:elseif test="surveyQuestionType.type == @com.reach150.enumeration.SurveyQuestionTypeEnum@SINGLESELECTDROPDOWN || surveyQuestionType.type == @com.reach150.enumeration.SurveyQuestionTypeEnum@MULTISELECTDROPDOWN" >
<s:select name="question_%{surveyQuestionId}" list="orderedSelectOptions" listKey="optionValue" listValue="optionLabel" emptyOption="true" multiple="true" cssClass="form-control" />
</s:elseif>
<s:else>
<s:radio name="question_%{surveyQuestionId}" list="#{'true':'Yes','false':'No'}" cssClass="radioMarginRight" />
</s:else>
<span class="help-block" for="question_${surveyQuestionId}">
<s:fielderror cssClass="font-bold text-danger">
<s:param>question_<s:property value="surveyQuestionId" /></s:param>
</s:fielderror>
</span>
<br/>
</p>
</s:iterator>
<button type="submit" class="btn btn-primary btn-lg pull-right "><s:if test="survey.requestReferral == true">Next</s:if><s:else>Done</s:else></button>
</s:form>
在提交表单时,在操作 class 中我们使用 HttpServletRequest
来获取提交的表单字段值。我们识别答案属于哪个问题的方法是通过请求参数名称,如上面的 JSP 片段所示,以前缀 'question_' 开头,后跟问题 ID。因此,我们拆分参数名称以获取问题 ID 并针对该问题关联值。
我们面临的问题是,在验证错误的情况下,当页面返回给用户时,用提交的值重新填充调查表,因为参数名称是动态的,不能由 Action [=60] 中定义的属性支持=].
我尝试使用以下代码和其他几种方法填充 radio button 和 textarea 字段,但无济于事
<s:textarea name="question_%{surveyQuestionId}" style="height: 150px; width: 400px;" cssClass="form-control" maxlength="charactersLimit" value="#parameters.%{'question_' + surveyQuestionId}" />
<s:radio name="question_%{surveyQuestionId}" value="#parameters.%{'question_' + surveyQuestionId}" list="#{'true':'Yes','false':'No'}" cssClass="radioMarginRight" />
以下是调查提交操作的操作映射
<action name="survey/submitFeedback" class="surveyAction" method="submitFeedback">
<result name="success" type="tiles">survey.submit</result>
<result name="error" type="tiles">survey.view</result>
<param name="public">true</param>
</action>
这是 Action class 中处理提交逻辑的代码:
private Integer npsScore = 0;
private Map<String, String[]> surveyResponseQuestionAnswerMap = new HashMap<>();
public String submitFeedback() {
try {
if (requestId == null) {
addActionError("Request Id missing! Invalid Request!");
throw new Exception("Invalid Request!");
}
surveyRequest = surveyService.getSurveyRequestByUUID(requestId);
if (surveyRequest == null) {
addActionError("Request Id Invalid! Invalid Request!");
throw new Exception("Request Id Invalid! Invalid Request!");
}
loadQuestionAnswersMap();
validateSurveyFeedback();
if (hasErrors()) {
throw new Exception("Error submitting response!");
} else {
surveyService.parseAndSaveSurveyResponse(surveyRequest, surveyResponseQuestionAnswerMap);
setSurveyCustomMessages(surveyService.getSurveyCustomMessagesSettingBySurveyId(survey.getSurveyId()));
}
return SUCCESS;
} catch (Exception e) {
addActionError("Error submitting response!");
logger.error(e);
loadSurvey();
return ERROR;
}
}
private void loadQuestionAnswersMap() {
HttpServletRequest httpRequest = ActionUtil.getRequest();
Enumeration<String> parameterNames = httpRequest.getParameterNames();
while (parameterNames.hasMoreElements()) {
String parameterName = parameterNames.nextElement();
if (parameterName.startsWith("question_")) {
String[] values = httpRequest.getParameterValues(parameterName);
if (values != null) {
surveyResponseQuestionAnswerMap.put(parameterName, values);
}
}
}
}
private void validateSurveyFeedback() throws Exception {
HttpServletRequest httpRequest = ActionUtil.getRequest();
Survey survey = surveyRequest.getSurvey();
if (survey.isUseNetPromotorScore()) {
String npsScoreStr = httpRequest.getParameter("npsScore");
if (StringUtils.isBlank(npsScoreStr)) {
this.addFieldError("npsScore", "Answer is required");
} else {
setNpsScore(Integer.valueOf(npsScoreStr));
}
}
List<SurveyQuestion> requiredQuestions = surveyQuestionService.getRequiredSurveyQuestionsForSurvey(surveyRequest.getSurvey());
for (SurveyQuestion requiredQuestion : requiredQuestions) {
Integer requiredQuestionId = requiredQuestion.getSurveyQuestionId();
String requiredQuestionFieldParameterName = "question_" + requiredQuestionId;
logger.info("Required Question Field Parameter Name: " + requiredQuestionFieldParameterName);
String[] answers = httpRequest.getParameterValues(requiredQuestionFieldParameterName);
if (answers == null) {
this.addFieldError(requiredQuestionFieldParameterName, "Answer is required");
} else {
boolean noValue = true;
for (String answer : answers) {
if (StringUtils.isNotBlank(answer)) {
noValue = false;
break;
}
}
if (noValue) {
this.addFieldError(requiredQuestionFieldParameterName, "Answer is required");
}
}
}
}
我们终于解决了这个问题。最初的实现朝着完全错误的方向前进,但感谢@RomanC 的线索,我们重构了代码并删除了 HttpServletRequest
的直接使用,最终有了一个可行的解决方案。核心思想是使用一个 Bean 来捕获响应,并在对应于每个调查问题的 Action class 中有一个 List
这些 bean。在表单提交时,响应被框架本身捕获到后台的 bean 对象中,因此可用于提交处理程序操作方法中的进一步逻辑处理。
我们有一个 Struts2 应用程序,我们正在构建调查功能,应用程序用户可以使用该应用程序通过添加不同的问题来创建调查。
这些问题由问题文本和用于获取响应的 html 控件组成。
到目前为止支持的 html 控件是 single 和 multi-select list, 文本字段、文本区域、复选框和单选框.
因此调查表呈现显示用户添加的所有问题,其中每个问题都显示其问题文本,后跟 html 字段控件 select 为该问题编辑。
基本上,它是一个动态表单,其中表单字段名称是动态生成的,因为所有调查都不同,因此 Action class 支持调查表单字段中没有属性。
我们正在生成表单字段名称,使用前缀 question_
附加问题的数据库 ID,以唯一地表示每个问题响应输入字段。这是我们 JSP 页面的一个片段,可以清楚地说明这一点。
<s:form id="surveyForm" action="survey/submitFeedback">
<s:iterator value="surveyQuestions">
<p class="form-group <s:if test="%{fieldErrors.get('question_' + surveyQuestionId).size() > 0}">has-error</s:if>" >
<label class="control-label" >
<s:property value="questionText"/>
</label>
<s:if test="required" ><span style='color:red'>*</span></s:if>
<br>
<s:if test="surveyQuestionType.type == @com.reach150.enumeration.SurveyQuestionTypeEnum@OPENENDED_TEXTFIELD" >
<s:textfield name="question_%{surveyQuestionId}" cssClass="form-control" maxlength="charactersLimit" />
</s:if>
<s:elseif test="surveyQuestionType.type == @com.reach150.enumeration.SurveyQuestionTypeEnum@OPENENDED_TEXTAREA" >
<s:textarea name="question_%{surveyQuestionId}" style="height: 150px; width: 400px;" cssClass="form-control" maxlength="charactersLimit" />
</s:elseif>
<s:elseif test="surveyQuestionType.type == @com.reach150.enumeration.SurveyQuestionTypeEnum@SINGLESELECTDROPDOWN || surveyQuestionType.type == @com.reach150.enumeration.SurveyQuestionTypeEnum@MULTISELECTDROPDOWN" >
<s:select name="question_%{surveyQuestionId}" list="orderedSelectOptions" listKey="optionValue" listValue="optionLabel" emptyOption="true" multiple="true" cssClass="form-control" />
</s:elseif>
<s:else>
<s:radio name="question_%{surveyQuestionId}" list="#{'true':'Yes','false':'No'}" cssClass="radioMarginRight" />
</s:else>
<span class="help-block" for="question_${surveyQuestionId}">
<s:fielderror cssClass="font-bold text-danger">
<s:param>question_<s:property value="surveyQuestionId" /></s:param>
</s:fielderror>
</span>
<br/>
</p>
</s:iterator>
<button type="submit" class="btn btn-primary btn-lg pull-right "><s:if test="survey.requestReferral == true">Next</s:if><s:else>Done</s:else></button>
</s:form>
在提交表单时,在操作 class 中我们使用 HttpServletRequest
来获取提交的表单字段值。我们识别答案属于哪个问题的方法是通过请求参数名称,如上面的 JSP 片段所示,以前缀 'question_' 开头,后跟问题 ID。因此,我们拆分参数名称以获取问题 ID 并针对该问题关联值。
我们面临的问题是,在验证错误的情况下,当页面返回给用户时,用提交的值重新填充调查表,因为参数名称是动态的,不能由 Action [=60] 中定义的属性支持=].
我尝试使用以下代码和其他几种方法填充 radio button 和 textarea 字段,但无济于事
<s:textarea name="question_%{surveyQuestionId}" style="height: 150px; width: 400px;" cssClass="form-control" maxlength="charactersLimit" value="#parameters.%{'question_' + surveyQuestionId}" />
<s:radio name="question_%{surveyQuestionId}" value="#parameters.%{'question_' + surveyQuestionId}" list="#{'true':'Yes','false':'No'}" cssClass="radioMarginRight" />
以下是调查提交操作的操作映射
<action name="survey/submitFeedback" class="surveyAction" method="submitFeedback">
<result name="success" type="tiles">survey.submit</result>
<result name="error" type="tiles">survey.view</result>
<param name="public">true</param>
</action>
这是 Action class 中处理提交逻辑的代码:
private Integer npsScore = 0;
private Map<String, String[]> surveyResponseQuestionAnswerMap = new HashMap<>();
public String submitFeedback() {
try {
if (requestId == null) {
addActionError("Request Id missing! Invalid Request!");
throw new Exception("Invalid Request!");
}
surveyRequest = surveyService.getSurveyRequestByUUID(requestId);
if (surveyRequest == null) {
addActionError("Request Id Invalid! Invalid Request!");
throw new Exception("Request Id Invalid! Invalid Request!");
}
loadQuestionAnswersMap();
validateSurveyFeedback();
if (hasErrors()) {
throw new Exception("Error submitting response!");
} else {
surveyService.parseAndSaveSurveyResponse(surveyRequest, surveyResponseQuestionAnswerMap);
setSurveyCustomMessages(surveyService.getSurveyCustomMessagesSettingBySurveyId(survey.getSurveyId()));
}
return SUCCESS;
} catch (Exception e) {
addActionError("Error submitting response!");
logger.error(e);
loadSurvey();
return ERROR;
}
}
private void loadQuestionAnswersMap() {
HttpServletRequest httpRequest = ActionUtil.getRequest();
Enumeration<String> parameterNames = httpRequest.getParameterNames();
while (parameterNames.hasMoreElements()) {
String parameterName = parameterNames.nextElement();
if (parameterName.startsWith("question_")) {
String[] values = httpRequest.getParameterValues(parameterName);
if (values != null) {
surveyResponseQuestionAnswerMap.put(parameterName, values);
}
}
}
}
private void validateSurveyFeedback() throws Exception {
HttpServletRequest httpRequest = ActionUtil.getRequest();
Survey survey = surveyRequest.getSurvey();
if (survey.isUseNetPromotorScore()) {
String npsScoreStr = httpRequest.getParameter("npsScore");
if (StringUtils.isBlank(npsScoreStr)) {
this.addFieldError("npsScore", "Answer is required");
} else {
setNpsScore(Integer.valueOf(npsScoreStr));
}
}
List<SurveyQuestion> requiredQuestions = surveyQuestionService.getRequiredSurveyQuestionsForSurvey(surveyRequest.getSurvey());
for (SurveyQuestion requiredQuestion : requiredQuestions) {
Integer requiredQuestionId = requiredQuestion.getSurveyQuestionId();
String requiredQuestionFieldParameterName = "question_" + requiredQuestionId;
logger.info("Required Question Field Parameter Name: " + requiredQuestionFieldParameterName);
String[] answers = httpRequest.getParameterValues(requiredQuestionFieldParameterName);
if (answers == null) {
this.addFieldError(requiredQuestionFieldParameterName, "Answer is required");
} else {
boolean noValue = true;
for (String answer : answers) {
if (StringUtils.isNotBlank(answer)) {
noValue = false;
break;
}
}
if (noValue) {
this.addFieldError(requiredQuestionFieldParameterName, "Answer is required");
}
}
}
}
我们终于解决了这个问题。最初的实现朝着完全错误的方向前进,但感谢@RomanC 的线索,我们重构了代码并删除了 HttpServletRequest
的直接使用,最终有了一个可行的解决方案。核心思想是使用一个 Bean 来捕获响应,并在对应于每个调查问题的 Action class 中有一个 List
这些 bean。在表单提交时,响应被框架本身捕获到后台的 bean 对象中,因此可用于提交处理程序操作方法中的进一步逻辑处理。