使用 AJAX 将行动态添加到表单中的 table

Dynamically add rows to table in form by using AJAX

我有一个包含 table 的表格。我希望能够通过发送 AJAX 请求并将结果附加到 table 正文来向此 table 动态添加行。此请求应触发模板的呈现并将相应的元素添加到我的数据传输对象。这使我能够在用户按下提交按钮时舒适地table 保存对象。

我的问题是我需要在控制器中定义哪种处理程序方法才能将此行添加到我的模型中?如何使用我的 AJAX 请求传递模型?这完全可以做到吗?

您可以在下方找到用于创建 table:

的代码片段
<div class="form-group form-row">
    <button type="submit" id="addAttribute" name="addAttribute" class="btn btn-success">
        <i class="fa fa-fw fa-plus"></i> New attribute
    </button>
</div>
<table>
    <thead>
        <tr>
            <th>&nbsp;</th>
            <th>Attribute name</th>
        </tr>
    </thead>
    <tbody id="attributeList">
    </tbody>
</table>

这是我的数据传输对象:

public class IEForm
{
    private String name;
    // ...

    // IEAttribute is defined somewhere else, but this doesn't matter right now
    private List<IEAttribute> attributes;

    // getters and setters

    public List<IEAttribute> getAttributes()
    {
        return this.attributes;
    }

    public void setAttributes(List<IEAttribute> attributes)
    {
        this.attributes = attributes;
    }
}

我的控制器用于创建表单页面和 AJAX 请求的处理程序方法:

@RequestMapping("/create")
String create(Model model)
{
    model.addAttribute("form", new IEForm());
    return "iecreation/creation";
}

@RequestMapping(value = "/addAttribute", params = {"addAttribute"})
public String addAttribute(IEForm dto, BindingResult result)
{
    dto.getAttributes().add(new IEAttribute());
    return "iecreation/newAttribute";
}

AJAX 页面:

<tr>
    <td><i class="fa fa-fw fa-key"></i></td>
    <td>
        <input type="text" class="form-control col-lg-8" placeholder="Attributname"
            th:field="???"/>
    </td>
</tr>

用于将 AJAX 响应添加到 table

的 JavaScript 代码
$(document).ready(function()
{
    $("#newAttribute").on("click", function()
    {
        $.ajax({
            type: "GET",
            url: "/ie/create/newAttribute",
            success: function(response)
            {
                $("#attributeList").append(response);
            }
        });
    });
});

我试过 this approach,但它没有按预期运行,因为我的表单不断提交(执行了错误的处理程序,需要重新加载页面)。

This answer couldn't help me, as it's valid for JSP.

一位同事建议添加大量隐藏的 div,然后通过单击按钮动态使它们可见。但是,我不喜欢这个解决方案,因为它不是很干净并且属性的数量受到限制。

感谢您的支持!

发送 ajax 后,您将从控制器获取列表对象,将该列表对象数据附加到 table 主体以便更好地使用 jquery。如下图

Jquery:
$(document).ready(function(){
     $.ajax({
            url: "yourURL", 
            type:"GET",
            .
            .
            .
             ,success: function(res){
                var index = 0;
                var subObj = '';
                var htm = '';
                for(index = 0; index < res.length; index++) {
                  subObj    =   res[index];

                  htm +='<tr>';
                       htm+='<td>'+subObj.value+'</td>';
                       htm+='<td>'+subObj.value+'</td>';
                       htm+='<td>'+subObj.value+'</td>';
                       htm+='<td>'+subObj.value+'</td>';
                       htm+='<td>'+subObj.value+'</td>';
                  htm+='</tr>';
              }
            $("table").find("tr:gt(1)").remove();
            $("table tbody").append(htm);

             },error:function(resp){


              }

        });


});

可能会有帮助。在这里,我使用 jquery+ajax.

将数据附加到 table 正文

我们终于走上了使用隐藏字段并通过 JavaScript 动态显示它们的道路。

控制器创建了一个大小为一百个 DTO 对象的列表。该模板呈现每个 DTO 对象,为其字段 属性 分配索引,并添加一个 CSS class,称为 d-none,使它们不可见。在th:field 属性中指定了数组的索引,因此Spring 可以正确地将表单数据映射到DTO。

<tr class="d-none form-group">
    <th><i class="fa fa-fw fa-minus-circle" th:id="|remove-${iterator.index}|"></i></th>
    <td>&nbsp;</td>
    <td>
        <input  type="text" class="form-control col-lg-8" placeholder="Attributname"
                th:field="*{attributes[__${iterator.index}__].name}"
                th:id="|attributeName-${iterator.index}|"/>
    </td>
</tr>

在 JavaScript 部分中,当用户在对象之间导航时,会动态添加和删除 d-none CSS class。我们通过属性的内容id得到当前可见属性的索引

根据要求,我附上了 DTO class 的源 class 以及为其创建的 100 个属性:

public class IEForm {
    private static final int ATTRIBUTE_MAX_COUNT = 100;

    @NotNull
    @NotEmpty
    @Valid
    private List<IEAttributeForm> attributes;

    public void removeCommaFromAttributeNames() {
        for (IEAttributeForm processed : this.attributes) {
            processed.setName(
                    processed.getName().substring(
                            0,
                            processed.getName().indexOf(',')
                    )
            );
        }
    }

    public List<IEAttributeForm> getFilledOutAttributes() {
        List<IEAttributeForm> filledOutAttributes = new ArrayList<>();
        int i = 0;
        while (
                (i < ATTRIBUTE_MAX_COUNT)
                && (this.attributes.get(i).getName() != null)
                && (this.attributes.get(i).getName().length() > 0)
                && (!Objects.equals(this.attributes.get(i).getPhysicalName(), IEAttributeForm.DEFAULT_NAME))
                && (!Objects.equals(this.attributes.get(i).getAttributeName(), IEAttributeForm.DEFAULT_NAME))
        ) {
            filledOutAttributes.add(this.attributes.get(i));
            ++i;
        }

        return filledOutAttributes;
    }

    public void initialize() {
        for (int i = NUMBER_OF_INITIAL_ATTRIBUTES; i < ATTRIBUTE_MAX_COUNT; ++i) {
            this.attributes.add(new IEAttributeForm(i));
        }
    }
}

这里是处理属性的控制器:

@Controller
@RequestMapping("/ie")
public class InformationEntityController {
    // ...

    @RequestMapping("/create")
    String create(@RequestParam(name = "search", required = false, defaultValue = "") String searchText, Model model) {
        IEForm infoEntity = new IEForm();
        infoEntity.initialize();
        this.initializeInfoEntityCreationFormParameters(model, infoEntity, searchText);
        return "iecreation/creation";
    }

    @RequestMapping("/save")
    String save(
            @Valid @ModelAttribute("form") IEForm dto,
            BindingResult bindingResult,
            Model model
    ) {
        dto.removeCommaFromAttributeNames();

        InfoEntity created = new InfoEntity();
        created.setDescription(dto.getDescription());
        created.setDisplayName(dto.getName());
        created.setVersion(dto.getVersionAlinter());
        created.setEditable(dto.isEditable());
        created.setActive(dto.isActive());
        created.setComplete(dto.isComplete());

        created.setNameALINTER(dto.getNameAlinter());
        created.setVersionALINTER(dto.getVersionAlinter());
        created.setNameCAR(dto.getCarObjectName());

        if (!bindingResult.hasErrors()) {
            Optional<Application> ieApplication = this.applicationRepository.findById(dto.getApplicationId());
            if (ieApplication.isPresent()) {
                created.setApplication(ieApplication.get());
            }

            ArrayList<IEAttribute> attributes = new ArrayList<>();
            for (IEAttributeForm processedAttribute : dto.getFilledOutAttributes()) {
                IEAttribute addedAttribute = new IEAttribute();

                addedAttribute.setType(processedAttribute.getDataType());
                addedAttribute.setAttributeName(processedAttribute.getName());
                addedAttribute.setPhysicalName(processedAttribute.getPhysicalName());
                addedAttribute.setPhysicalLength(processedAttribute.getPhysicalLength());
                addedAttribute.setFieldType(processedAttribute.getFieldType());
                addedAttribute.setAttributeDescription(processedAttribute.getAttributeDescription());
                addedAttribute.setHasHW(processedAttribute.getHasHint());
                addedAttribute.setMaxLength(processedAttribute.getMaxLength());
                addedAttribute.setEnumName(processedAttribute.getEnumName());
                addedAttribute.setPrecision(processedAttribute.getPrecision());

                attributes.add(addedAttribute);
            }
            created.setAttributes(attributes);

            this.ieService.saveInfoEntity(created);
            return "redirect:/download/ie?infoEntityId=" + created.getId();
        }
        else {
            this.initializeInfoEntityCreationFormParameters(model, dto, "");
            return "iecreation/creation";
        }
    }
}

最后,这是在属性之间切换的TypeScript代码:

const ADDITIONAL_ATTRIBUTE_COUNT = 100;
let visibleAttributeCount = 0;
let previousAttributeIndex = (-1);

function initializeAttributes() {
    visibleAttributeCount = getNumberOfActiveAttributes();
    for (let i = 0; i < visibleAttributeCount; ++i) {
        addAttribute(i);
    }
    getTableNode(0).removeClass("d-none");
    applyValidationConstraints(0);
    previousAttributeIndex = 0;

    $("#addAttribute").on("click", function(event): boolean {
        event.preventDefault();

        addAttribute(visibleAttributeCount);
        ++visibleAttributeCount;
        return false;
    });

    const totalAttributeCount = ADDITIONAL_ATTRIBUTE_COUNT + visibleAttributeCount;
    const INDEX_ID_ATTRIBUTE = 1;
    for (let i = 0; i < totalAttributeCount; ++i) {
        $("#previousTable-" + i).on("click", function(event) {
            event.preventDefault();
            let attributeId = extractAttributeId(event.target.attributes[INDEX_ID_ATTRIBUTE].value);
            if (attributeId > 0) {
                --attributeId;
                switchToTable(attributeId);
            }
        });
        $("#nextTable-" + i).on("click", function(event) {
            event.preventDefault();
            let attributeId = extractAttributeId(event.target.attributes[INDEX_ID_ATTRIBUTE].value);
            if (attributeId < (visibleAttributeCount - 1)) {
                ++attributeId;
                switchToTable(attributeId);
            }
        });
        $("#attributeName-" + i).on("keyup", function(event) {
            event.preventDefault();
            for (let processedAttribute of event.target.attributes) {
                if (processedAttribute.name === "id") {
                    let attributeId = extractAttributeId(processedAttribute.value);
                    $("#tableHeading-" + attributeId).text(String($(event.target).val()));
                }
            }
        })
        $("#attributes-" + i + "_dataType").on("change", function(event) {
            event.preventDefault();
            for (let processedAttribute of event.target.attributes) {
                if (processedAttribute.name === "id") {
                    let attributeId = extractAttributeId(processedAttribute.value);
                    applyValidationConstraints(attributeId);
                }
            }
        });
    }
}

function getNumberOfActiveAttributes(): number {
    let numberOfActiveAttributes = 0;

    const attributeRows = $("#attributeList");
    attributeRows.children("tr").each(function(index, element) {
        const attributeName: string = String($(element).children("td").last().children("input").val());
        if ((attributeName != null) && (attributeName !== "")) {
            ++numberOfActiveAttributes;
        }
    });

    return numberOfActiveAttributes;
}

function addAttribute(attributeIndex: number) {
    let rowNode = getAttributeRow(attributeIndex);
    rowNode.removeClass("d-none");
    $("td:eq(1)>input", rowNode).attr("required", "");
}

function getAttributeRow(attributeNumber: number) {
    return $("#attributeList>tr:eq(" + attributeNumber + ")");
}

function extractAttributeId(fullyQualifiedId: string): number {
    return parseInt(fullyQualifiedId.substring((fullyQualifiedId.indexOf("-") + 1)));
}

function switchToTable(attributeIndex: number) {
    if (previousAttributeIndex !== (-1)) {
        getTableNode(previousAttributeIndex).addClass("d-none");
    }
    previousAttributeIndex = attributeIndex;

    let currentTableNode = getTableNode(attributeIndex);

    applyValidationConstraints(attributeIndex);
    currentTableNode.removeClass("d-none");
}

function getTableNode(attributeIndex: number): JQuery<HTMLElement> {
    return $("#table-" + attributeIndex);
}

function applyValidationConstraints(attributeId: number) {
    const currentDataTypeName = String($("#attributes-" + attributeId + "_dataType").val());
    if (validationRules.has(currentDataTypeName)) {
        $(".affectedByDataType-" + attributeId).addClass("d-none");
        const dataType = validationRules.get(currentDataTypeName);
        if (dataType != null) {
            for (let processedAttribute of dataType.attributes) {
                makeAttributeVisible(attributeId, processedAttribute);
            }
        }
    } else {
        $(".affectedByDataType-" + attributeId).removeClass("d-none");
    }
}

function makeAttributeVisible(attributeId: number, processedAttribute: ValidationAttribute) {
    $("#attributeRow-" + attributeId + "_" + processedAttribute.id).removeClass("d-none");
    const attributeInputNode = $("#attributes-" + attributeId + "_" + processedAttribute.id);
    attributeInputNode.attr("type", processedAttribute.type);
    if (processedAttribute.pattern != null) {
        attributeInputNode.attr("pattern", processedAttribute.type);
    } else {
        attributeInputNode.removeAttr("pattern");
    }
}