ASP.NET MVC; using EditorTemplates to edit a collection, best way for dynamic field creation
Asked Answered
E

3

8

I am new to ASP.NET MVC, so apologies if this is easier than it looks. I've been Googling around, I have a class like so:

public class Search : IAuditable
{
    [Key]
    public int SearchID { get; set; }
    public int UserID { get; set; }

    ...

    public ICollection<SearchTerm> SearchTerms { get; set; }   
}

In the Create.cshtml, I have the following

<div class="editor-label">
    @Html.LabelFor(model => model.Search.SearchTerms)
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.Search.SearchTerms, "SearchTerm")
    @Html.ValidationMessageFor(model => model.Search.SearchTerms)
</div>

SearchTerm EditorTemplate is a simple form like so:

@model Core.Search.Parameters.SearchTerm

@Html.HiddenFor(n => n.SearchTermID)
<div class="editor-label">
    @Html.LabelFor(n => n.Text, "Term")
</div>
<div class="editor-field">
    @Html.EditorFor(n => n.Text)
    @Html.ValidationMessageFor(n => n.Text)
</div>

It seems to work, I see a single text box on create (when the default model is empty). However, what I want to do is be able to add / remove the SearchTerms with an Add button so the user can add an arbitrary amount of terms to the collection on create. Is this built in somehow? is there a javascript framework that pairs well with this, that will generate the appropriate html names so I don't have to manually do that? Am I even approaching this the right way, given that I am new to MVC?

Thanks!

Edmonson answered 2/5, 2013 at 0:41 Comment(4)
you need to take helpf JavaScript/jQuery here. On Add button click event use jquery clone function to append a new row of input fields to the form.Ferromagnetic
I think your approach is about right, thing is though that EditorTemplates are used server side, for generating the HTML to be served. There are many javascript framework that can help you out on the client side. You should take a look at Knockoutjs, which actually ships with asp.net MVC nowDomel
Here, read this blog.stevensanderson.com/2010/01/28/… And if you wanna do this purely on the client side without any AJAX read this: blog.stevensanderson.com/2010/07/12/…Putrescent
Have a look at this one as well demos.telerik.com/kendo-ui/web/multiselect/index.htmlMauriciomaurie
D
6

In this case I preferred to use PartialView.

You should create an ActionResult which return you PartialView for your item. And when you click "ADD" button you should send ajax request to this ActionResult.

public ActionResult GetTermItem()
{
    return PartialView("_SearchTerm", new SearchTerm());
}

Your PartialView should looks like this:

@model Core.Search.Parameters.SearchTerm
@{ ViewContext.FormContext = new FormContext(); }
@using (Html.BeginCollectionItem("SearchTerms"))
{
    <div class="search-term-item">            
       @Html.HiddenFor(n => n.SearchTermID)
       <div class="editor-label">
          @Html.LabelFor(n => n.Text, "Term")
       </div>
       <div class="editor-field">
          @Html.EditorFor(n => n.Text)
          @Html.ValidationMessageFor(n => n.Text)
       </div>

       <a href="javascript:void(0);" class="remove-search-item" title="Remove">Remove</a>
    </div>
}

Your main page:

<div class="editor-label">
    @Html.LabelFor(model => model.Search.SearchTerms)
</div>
<div id="search-terms-container" class="editor-field">
    @if (Model.SearchTerms != null && Model.SearchTerms.Any())
    {
        foreach (var item in Model.SearchTerms)
        {
             @Html.Partial("_SearchTerm", item)
        }
    }
</div>
<a href="javascript:void(0);" id="add-search-item">Add</a>

And your JavaScript (jQuery) part:

$(document).on('click', '#add-search-term', function () {
    $.ajax({
        url: '@Url.Action("GetTermItem")',
        cache: false,
        success: function (data) {
            var cl = $('#search-terms-container');
            cl.append(data);
            var terms = cl.find('.search-term-item');
            terms.last().hide().show('blind');
        }
    });
    return false;
});

$(document).on('click', '.remove-search-term', function () {
    $(this).closest('.search-term-item').hide('blind', function () { $(this).remove(); });
    var cl = $('#search-terms-container');
    var terms= cl.find('.search-term-item');
    return false;
});
Deliladelilah answered 17/2, 2014 at 11:45 Comment(0)
I
0

If you want to truly model bind this you will need to make use of Ajax or JQuery/javavscript. On add you can perform:

$(".editor-field").load(url, function(){ // anonymous return call });

Or it may be simpler to merely add via javascript the search terms. So long as the names of the elements match, MVC will send the data to the server as IEnumerable. So if your model contains SearchTerms, the EditorFor will be appending each items name with an index i.e. SearchTerms[0].Name. This way all the mappings happen essentially behind the scenes, however, it is possible to clone your terms and modify

$("#YourClone").attr("name", "SearchTerms" + index);
Isolt answered 13/2, 2014 at 15:7 Comment(0)
M
0

I've done a similar thing with address in a user form, add as many address as you click on the + button, this is the code

View

$(document).on("click", "#addRowInd", function (ev) {
    ev.preventDefault();
    var index = (($("input[name^='Indirizzi']").filter(":hidden").length));
    var input = "<table>";
    input += "<tr>";
    input += "<td><label for=\"Indirizzi_"+index+"__Descrizione\">Descrizione</label></td>";
    input += "<td><input id=\"Indirizzi_" + index + "__Descrizione\" type=\"text\" name=\"Indirizzi[" + index + "].Descrizione\"></td>";
    input += "</tr>";
    input += "<tr>";
    input += "<td><label for=\"Indirizzi_" + index + "__Citta\">Citta</label></td>";
    input += "<td><input id=\"Indirizzi_" + index + "__Citta\" type=\"text\" name=\"Indirizzi[" + index + "].Citta\"></td>";
    input += "</tr>";
    input += "<tr>";
    input += "<td><label for=\"Indirizzi_" + index + "__Provincia\">Provincia</label></td>";
    input += "<td><input id=\"Indirizzi_" + index + "__Provincia\" type=\"text\" name=\"Indirizzi[" + index + "].Provincia\"></td>";
    input += "</tr>";
    input += "<tr>";
    input += "<td><label for=\"Indirizzi_" + index + "__Cap\">Cap</label></td>";
    input += "<td><input id=\"Indirizzi_" + index + "__Cap\" type=\"text\" name=\"Indirizzi[" + index + "].Cap\"></td>";
    input += "</tr>";
    input += "<tr>";
    input += "<td><label for=\"Indirizzi_" + index + "__Via\">Via</label></td>";
    input += "<td><input id=\"Indirizzi_" + index + "__Via\" type=\"text\" name=\"Indirizzi[" + index + "].Via\"></td>";
    input += "</tr>";
    input += "</table>";
    $('#tabella').find('tr.indirizzo:last').after("<tr class=\"indirizzo\"><td>Indirizzo <img src=\"/Content/layout/ico-cancella-8.png\" class=\"cancInd\" id=\"" + index + "\" /></td><td>" + input + "</td></tr>");
});

[...]

<tr class="indirizzo">
    <td>@Html.LabelFor(m => Model.Indirizzi) <input id="addRowInd" type="button" /></td>
    <td>@Html.EditorFor(m => m.Indirizzi[0])</td>
</tr> 

Model

public class ContattoModel
{
    [...]
    [Display(Name = "Indirizzo")]
    public List<Indirizzi> Indirizzi { get; set; }
    [...]
}

and the controller

public ActionResult ContattiForm([ModelBinder(typeof(ContattiBinder))]ContattoModel model){

 [...]
}

so in my model.Indirizzi i got all the address that i've added via + button

Muslim answered 13/2, 2014 at 16:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.