Search code examples
asp.net-coreasp.net-ajaxrazor-pagesdynamic-html

How to add multiple entities in Razor Pages with dynamic HTML to the editable entity?


I have an ASP.NET Core 3.0 web application with generated razor pages. The model is the following:

public class Element
{
    public int ElementID { get; set; }
    public string Name { get; set; }
    public string Discriminator { get; private set; }
}

public class BucketListElement : Element
{
    public int BucketListID { get; set; }
    public string Description { get; set; }
    public bool Completed { get; set; }

    public BucketList BucketList { get; set; }
    public Progression Progression { get; set; }
}

public class Progression
{
    public int ProgressionID { get; set; }
    public int ElementID { get; set; }
    public ICollection<BLETask> BLETasks { get; set; }
    public BucketListElement BLElement { get; set; }
}

public class BLETask
{
    public int BLETaskID { get; set; }
    public int ProgressionID { get; set; }
    public string Text { get; set; }
    public bool Completed { get; set; }
    public Progression Progression { get; set; }
}

A BucketListElement has 1 Progression. A progression can have 0..* BucketListElementTasks (BLETask). After creating a BucketListElement, we can navigate to the Edit page, and there I would like to be able to do the following: The user could press a "+" sign, that adds one more field with a checkbox, or could press a "-" sign that removes the field with the checkbox as well next to it. Every field needs to be filled, and after saving as many BLETasks should be assigned to the edited BucketListElement's Progression entity (through the foreign key) as many fields the user created and filled.

Edit page layout after clicking the "+" sign twice (red part is needed)

If I know it right I can manipulate the HTML dinamically to add and remove those fields with JavaScript / Ajax, but unfortunately I do not have experience in that, this is why I ask your help.

UPDATE: With the suggested changes of @mj1313 adding BLETasks was working fine, but I wanted to be able to edit already registered BLETasks, so I inserted this code into the Edit.cshtml into the with the id of "Tasks". Now it looks like this:

<div class="form-group">
<label asp-for="BucketListElement.Progression.BLETasks" class="control-label"></label>
<button id="addTask" class='btn btn-primary'>+</button>
<div id="Tasks" style="margin-top:10px">
    @{
        int indexCounter = 0;
        int taskCounter = 1;
        foreach (var task in Model.BucketListElement.Progression.BLETasks)
        {
            <div class="taskRow">
                <label>Task @taskCounter:</label>
                <input class="form-control" asp-for="@Model.BucketListElement.Progression.BLETasks[indexCounter].Text" required="required" />
                <input class="largerCheckbox" asp-for="@Model.BucketListElement.Progression.BLETasks[indexCounter].Completed" />
                <button class="btn btn-danger" style="padding-top:1px" onclick="deleteRow(this)" value="@indexCounter">-</button>
            </div>
            indexCounter++;
            taskCounter++;
        }
    }
</div>

And now from the already existing BLETasks only the last row can be deleted (and the ones that were created by the script ("+" button)). If I press a "-" button at the end of the first n-1 row, it is like if I clicked on the Save button. If I delete one required field's text (to be able to stay on the page) and click on one of the first n-1 row's "-" button, this happens: After deleting a required field and deleting one of the first n-1 row


Solution

  • I made an example based on your description, you can refer to the below codes:

    Edit.cshtml:

    @page
    @model WebApplication9.Pages.EditModel
    @{
    }
    
    <style>
        .largerCheckbox {
            width: 25px;
            height: 25px;
            margin-left: 20px;
            margin-right: 20px;
            vertical-align: middle
        }
    
    </style>
    
    <div class="row">
        <div class="col-md-4">
            <form method="post">
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <div class="form-group">
                    <label asp-for="BucketListElement.Name" class="control-label"></label>
                    <input asp-for="BucketListElement.Name" class="form-control" />
                    <span asp-validation-for="BucketListElement.Name" class="text-danger"></span>
                </div>
                <div class="form-group">
                    <label asp-for="BucketListElement.Description" class="control-label"></label>
                    <input asp-for="BucketListElement.Description" class="form-control" />
                    <span asp-validation-for="BucketListElement.Description" class="text-danger"></span>
                </div>
                <div class="form-group">
                    <label asp-for="BucketListElement.Progression.BLETasks" class="control-label"></label>
                    <button id="addTask" class='btn btn-primary'>+</button>
                    <div id="Tasks" style="margin-top:10px">
    
                    </div>
                </div>
                <div class="form-group">
                    <input type="submit" value="Create" class="btn btn-primary" />
                </div>
            </form>
        </div>
    </div>
    
    
    @section scripts{
        <script>
            var count = 1;
            $("#addTask").on("click", function () {
                var htmlstring = "<div class='taskRow'><label>Task" + count + "</label>";
                htmlstring += "<input type='text' name='BucketListElement.Progression.BLETasks[" + (count - 1) +"].Text' required='required'>";
                htmlstring += "<input type='checkbox' name='BucketListElement.Progression.BLETasks[" + (count - 1) +"].Completed' class='largerCheckbox' value='true'>";
                htmlstring += "<button class='btn btn-danger' style='padding-top:1px' onclick='deleteRow(this)' value='" + (count - 1)+"'>-</button></div>"
                $("#Tasks").append(htmlstring);
                count++;
            })
    
            function deleteRow(element) {
                var currentRow = parseInt($(element).val());
                let nextsiblings = document.querySelectorAll('.taskRow:nth-child(' + (currentRow + 1) + ') ~ *');
                for (var i = 0; i < nextsiblings.length; i++) {
                    var childElements = nextsiblings[i].children;
                    childElements[0].textContent = "Task" + (currentRow + 1);
                    childElements[1].setAttribute("name", "BucketListElement.Progression.BLETasks[" + currentRow + "].Text");
                    childElements[2].setAttribute("name", "BucketListElement.Progression.BLETasks[" + currentRow + "].Completed");
                    childElements[3].setAttribute("value", currentRow)
                    currentRow++;
                }
                $(element).parent("div").remove();
                count--;
            }
        </script>
    
    }
    

    Edit.cshtml.cs:

    public class EditModel : PageModel
    {
        [BindProperty]
        public BucketListElement BucketListElement { get; set; }
    
        public void OnGet()
        {
            BucketListElement = new BucketListElement
            {
                Name = "Element1",
                Description = "AAA"
            };
        }
    
        public void OnPost()
        {
            
        }
    }
    

    Result:

    enter image description here