我是 Struts2 网络开发的初学者。为了学习,我正在开发一个食谱商店应用程序。我可以使用 JDBC 将 CRUD 基础 Material 导入到 MySQL 数据库中。现在我有一个 JSP 页面,用户可以在其中从基础 Material 创建配料以形成新配方。在我的程序中,一种成分是一种基础 Material ,它具有数量和单位名称集。 (在用户创建基础 Material 之前,他可以在默认单位名称(g、dkg、kg)旁边指定一个额外的单位名称(例如杯子、茶匙、玻璃杯等)。
我有一个 Struts2 doubleselect
,其中第一个选择包含基本 Material 名称,第二个选择包含属于所选基本 Material 的相关单元名称。我也有数量输入文本字段。
从这 3 个输入,我可以创建一行新成分并将其动态添加到 JSP 中的 HTML 表中。表中的输入是附加到可见单元格值之后的单元格的隐藏文本输入。它们是隐藏的,因为我不希望它们是可编辑的,并且禁用不可编辑的文本字段不允许提交它们的值。此外,每一行在创建时都会获得一个删除 anchor 。每次删除后,都会重新计算隐藏输入元素名称中的索引。 Click me for the view of new recipe creation JSP
当没有删除行,或者删除的行是最后一行时,我可以提交输入,接受并在结果 JSP 中使用 struts2 操作打印它。否则,如果我删除动态成分列表中间或开头的一行,我会在提交表单后得到一个 NullPointerException
。如果我删除最后一行并在之后提交,它会完美地工作,就像我没有删除任何行一样。
那么,我在这里缺少什么?我没有正确删除表格行吗?我应该怎么做?
newrecipe.jsp
相关部分:
<script>
function addRow(tableID) {
//get material quantity value from quantity field
var materialQuantity = document.getElementById("materialQuantity");
/*VALIDATION OF QUANTITY FIELD: must be number, cannot be empty
[better to turn off validation until development is ongoing,
this is just set here for clarity while asking for help]*/
if (materialQuantity.value == "") {
alert("Add meg a hozzávaló mennyiségét!");
}
else if (isNaN(materialQuantity.value) == true) {
alert("A mennyiség csak szám lehet!");
}
else {
//get selected material name value
var selectedMaterial = $("#recipeSelect1 :selected").text()
//get selected unit name value
var selectedUnitName = $("#recipeSelect2 :selected").text()
//get table object
var table = document.getElementById(tableID);
var rowCount = table.rows.length;
var row = table.insertRow(rowCount);
var counts=rowCount;
/*create the cells of a new ingredient dynamic table row and
insert, append a hidden text input into it beside the visible values*/
var cell1 = row.insertCell(0);
var ingredientName = document.createElement("input");
ingredientName.type = "hidden";
ingredientName.name="recipe.ingredients["+counts+"].ingredientName";
ingredientName.value=selectedMaterial;
$(cell1).append(ingredientName);
$(cell1).append(selectedMaterial);
var cell2 = row.insertCell(1);
var ingredientUnitName = document.createElement("input");
ingredientUnitName.type = "hidden";
ingredientUnitName.name="recipe.ingredients["+counts+"].ingredientUnitName";
ingredientUnitName.value=selectedUnitName;
ingredientUnitName.className="materialFields";
$(cell2).append(ingredientUnitName);
$(cell2).append(selectedUnitName);
var cell3 = row.insertCell(2);
var ingredientQuantity = document.createElement("input");
ingredientQuantity.type = "hidden";
ingredientQuantity.name="recipe.ingredients["+counts+"].ingredientQuantity";
ingredientQuantity.value=materialQuantity.value;
ingredientQuantity.className="materialFields";
$(cell3).append(ingredientQuantity);
$(cell3).append(materialQuantity.value);
//create the dynamic anchor element for deletion of table row
var cell4 = row.insertCell(3);
var delIR = document.createElement("a");
delIR.href = "#";
delIR.className = "delIR";
delIR.innerHTML="Törlés";
delIR.onclick = function () {
//delete row
$(cell4).closest('tr').remove();
//recalculate name index after deletion of a row
for(var i=0; i < table.rows.length; i++) {
var inp0 = table.rows[i].cells[0].getElementsByTagName("input");
inp0.name = "recipe.ingredients["+i+"].ingredientName";
var inp1 = table.rows[i].cells[1].getElementsByTagName("input");
inp1.name="recipe.ingredients["+i+"].ingredientUnitName";
var inp2 = table.rows[i].cells[2].getElementsByTagName("input");
inp2.name="recipe.ingredients["+i+"].ingredientQuantity";
table.rows[i].cells[4].innerHTML = inp0.name;
}
};
$(cell4).append(delIR);
//print ingredient name for test purposes
var cell5 = row.insertCell(4);
$(cell5).append(ingredientName.name);
//empty quantity field after creation of row
materialQuantity.value = "";
}
}
</script>
</head>
<body>
<h1><s:property value="#materialTitle"/></h1>
<s:form action="saverecipe" method="post" id="recipeForm" theme="simple">
<div class="recipeLeftDiv">
<div class="recipeNameDiv">
<label class="recipeLabel" for="recipeNameField">Recept neve:</label>
<s:textfield
cssClass="recipeNameField"
id="recipeNameField"
name="recipe.recipeName"
/>
</div>
<s:doubleselect
id="recipeSelect1"
cssClass="recipeSelect1"
name="recipeSelect1"
list="materialNameUnitNameMap.keySet()"
doubleId="recipeSelect2"
doubleCssClass="recipeSelect2"
doubleName="recipeSelect2"
doubleList="materialNameUnitNameMap.get(top)"
theme="css_xhtml"
>
</s:doubleselect>
<s:textfield
id="materialQuantity"
cssClass="materialFields"
>
</s:textfield>
<button type="button" onclick="addRow('ingredientTable')">Hozzáadás</button>
<table id="ingredientTable" class="ingredientTable">
</table>
</div>
SaveRecipeAction.java
:
package actions;
import com.opensymphony.xwork2.ActionSupport;
import beans.Ingredient;
import beans.Recipe;
public class SaveRecipeAction extends ActionSupport {
private Recipe recipe;
public Recipe getRecipe() {
return recipe;
}
public void setRecipe(Recipe recipe) {
this.recipe = recipe;
}
public String execute() {
System.out.println("a hozzávalók lista mérete: "+recipe.getIngredients().size());
for (Ingredient ing:recipe.getIngredients()) {
System.out.print(ing.getIngredientName()+" ");
System.out.print(ing.getIngredientQuantity()+" ");
System.out.println(ing.getIngredientUnitName());
}
return SUCCESS;
}
}
Recipe.java
:
package beans;
import java.util.List;
public class Recipe {
private String recipeName;
private List<Ingredient> ingredients;
private String recipeDesc;
public String getRecipeName() {
return recipeName;
}
public void setRecipeName(String recipeName) {
this.recipeName = recipeName;
}
public List<Ingredient> getIngredients() {
return ingredients;
}
public void setIngredients(List<Ingredient> ingredients) {
this.ingredients = ingredients;
}
public String getRecipeDesc() {
return recipeDesc;
}
public void setRecipeDesc(String recipeDesc) {
this.recipeDesc = recipeDesc;
}
}
Ingredient.java
:
package beans;
public class Ingredient {
private double ingredientQuantity;
private String ingredientName;
private String ingredientUnitName;
public String getIngredientName() {
return ingredientName;
}
public void setIngredientName(String ingredientName) {
this.ingredientName = ingredientName;
}
public double getIngredientQuantity() {
return ingredientQuantity;
}
public void setIngredientQuantity(double ingredientQuantity) {
this.ingredientQuantity = ingredientQuantity;
}
public String getIngredientUnitName() {
return ingredientUnitName;
}
public void setIngredientUnitName(String ingredientUnitName) {
this.ingredientUnitName = ingredientUnitName;
}
}
Edit1: stacktrace 它对我说,当我试图打印该名称进行测试时,它没有在成分列表中找到该成分的名称(已删除的名称) SaveRecipeAction
中的目的。这意味着,在提交表格后,它将尝试设置错误的成分数量。它尝试根据“较早/删除前状态”-表的长度将成分数设置到列表中。
说,如果我在配料表中有 3 个项目,就像我附上的屏幕截图一样,删除中间的项目,然后提交表单,它将只打印第一个元素。首先我想,这背后的原因是因为当我测试程序时,我导航回上一页,到那时列表已经提交,一旦我应该删除更深的内容(也在食谱 bean 的列表中)。这甚至不应该发生,因为 JS 脚本只是客户端。但是即使在重新启动tomcat,重新加载页面后,也会出现此问题。
Struts Problem Report Struts has detected an unhandled exception: Messages: File: actions/SaveRecipeAction.java Line number: 21 Stacktraces java.lang.NullPointerException actions.SaveRecipeAction.execute(SaveRecipeAction.java:21) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) java.lang.reflect.Method.invoke(Method.java:606) com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:450) com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:289) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:252) org.apache.struts2.interceptor.debugging.DebuggingInterceptor.intercept(DebuggingInterceptor.java:256) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor.doIntercept(DefaultWorkflowInterceptor.java:167) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) com.opensymphony.xwork2.validator.ValidationInterceptor.doIntercept(ValidationInterceptor.java:265) org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor.doIntercept(AnnotationValidationInterceptor.java:68) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor.intercept(ConversionErrorInterceptor.java:138) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:239) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:239) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) com.opensymphony.xwork2.interceptor.StaticParametersInterceptor.intercept(StaticParametersInterceptor.java:191) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) org.apache.struts2.interceptor.MultiselectInterceptor.intercept(MultiselectInterceptor.java:73) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) org.apache.struts2.interceptor.CheckboxInterceptor.intercept(CheckboxInterceptor.java:91) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) org.apache.struts2.interceptor.FileUploadInterceptor.intercept(FileUploadInterceptor.java:252) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor.intercept(ModelDrivenInterceptor.java:100) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor.intercept(ScopedModelDrivenInterceptor.java:141) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) com.opensymphony.xwork2.interceptor.ChainingInterceptor.intercept(ChainingInterceptor.java:145) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) com.opensymphony.xwork2.interceptor.PrepareInterceptor.doIntercept(PrepareInterceptor.java:171) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) com.opensymphony.xwork2.interceptor.I18nInterceptor.intercept(I18nInterceptor.java:161) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) org.apache.struts2.interceptor.ServletConfigInterceptor.intercept(ServletConfigInterceptor.java:164) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) com.opensymphony.xwork2.interceptor.AliasInterceptor.intercept(AliasInterceptor.java:193) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor.intercept(ExceptionMappingInterceptor.java:189) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246) org.apache.struts2.impl.StrutsActionProxy.execute(StrutsActionProxy.java:54) org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:563) org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:77) org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:99) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100) org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953) org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041) org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603) org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312) java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) java.lang.Thread.run(Thread.java:744)
Edit 2: Output of the first line of the execution method of SaveRecipeAction action, in case of first deleting the middle ingredient from the sample 3-rows-long table and then submitting the form:
a hozzávalók lista mérete: 3
that is, the "size of the ingredient-list is: 3"
Edit 3: After modifying the index-recalculate block with alert(inp0.length)
it will output 1
two times.
//recalculate name index after deletion of a row
for(var i=0; i < table.rows.length; i++) {
var inp0 = table.rows[i].cells[0].getElementsByTagName("input");
inp0.name = "recipe.ingredients["+i+"].ingredientName";
alert(inp0.length);
var inp1 = table.rows[i].cells[1].getElementsByTagName("input");
inp1.name="recipe.ingredients["+i+"].ingredientUnitName";
var inp2 = table.rows[i].cells[2].getElementsByTagName("input");
inp2.name="recipe.ingredients["+i+"].ingredientQuantity";
table.rows[i].cells[4].innerHTML = inp0.name;
}
编辑 4:alert(inp0[0].length)
输出“未定义”。 inp0[1].length
和上面的索引(检查到索引 5
),根本不输出 alertbox。
最佳答案
不行,你应该改一下这段代码
for(var i=0; i < table.rows.length; i++) {
var inp0 = table.rows[i].cells[0].getElementsByTagName("input");
inp0[0].name = "recipe.ingredients["+i+"].ingredientName";
var inp1 = table.rows[i].cells[1].getElementsByTagName("input");
inp1[0].name="recipe.ingredients["+i+"].ingredientUnitName";
var inp2 = table.rows[i].cells[2].getElementsByTagName("input");
inp2[0].name="recipe.ingredients["+i+"].ingredientQuantity";
table.rows[i].cells[4].innerHTML = inp0[0].name;
}
它将输入 name
属性设置为 OGNL 表达式,其索引属性名称适合填充操作。
关于javascript - 删除行并提交空指针异常后使用javascript的struts2动态html表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21072280/