Create new parent and child on the same page

Sharon Eden  picture Sharon Eden · Jan 27, 2013 · Viewed 13.1k times · Source

My MVC application has a classic parent-child (master-detail) relations.

I want to have a single page that create both the new parent and the children on the same page. I have added an action the returns a partial view that and adds the child HTML to the parent’s view, but I don’t know how to associate the newly created child in the action to the original parent (in other word, how to I add the new child entity to the collection of these entities in the parent entity).

I guess that when I submit the form the action should get the parent entity with the newly created children in its collection.

So to make things short, what should be the code of the action that creates the child entity and how is the child added to its parent collection?

I have read a lot of posts here (and other sites) and couldn’t find an example.

The application uses MVC 4 and Entity Framework 5.

Code sample (I removed some of the code the keep it simple). The model is Form (parent) and FormField (child) entities.

public partial class Form
{ 
    public int ID { get; set; }
    public string Name { get; set; }

    public virtual ICollection<FormField> FormFields { get; set; }
}

public partial class FormField
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int FormID { get; set; }
}

The following partial view (_CreateFormField.cshtml) creates new FormField (child).

@model FormField

@using (Html.BeginForm()) {
@Html.ValidationSummary(true)

<fieldset>
    <legend>FormField</legend>

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

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


</fieldset>
}

And the following view (Create.cshtml) is the one the creates the Form.

@model Form

@using (Html.BeginForm()) {
@Html.ValidationSummary(true)

<fieldset>
    <legend>Form</legend>

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

<div>
        @Html.ActionLink(
                "Add Field",
                "CreateFormField", 
                new { id = -1},
                new { @class = "form-field" })
    </div>
    <p>
        <input type="submit" value="Create" />
    </p>
</fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

<div id="CreateFormField"></div>

@section Scripts {
<script>
    $(function () {
        $('.form-field').on('click', function (e) {

            $.get($(this).prop('href'), function (response) {
                $('#CreateFormField').append(response)
            });

            e.preventDefault();
        });
    });
</script>

@Scripts.Render("~/bundles/jqueryval")

}

The following actions handle the creation in the FormController.

[HttpPost]
public ActionResult Create(Form form)
{
    if (ModelState.IsValid)
    {
        db.Forms.Add(form);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(form);
}

public ActionResult CreateFormField(string id = null)
{
    // I guess something is missing here.

    return PartialView("_CreateFormField", new FormField());
}

Thanks in advance,

Sharon.

Answer

Amin Saqi picture Amin Saqi · Jul 27, 2013

I think the best and simplest way for you is that you have a view for creating Form and at the bottom of it put a fieldset to assign FormFields to it.

For the fieldset, you should have two partial views: One for create and another for edit. The partial view for creating should be something like this:

@model myPrj.Models.Form_FormFieldInfo

@{ 
    var index = Guid.NewGuid().ToString(); 
    string ln = (string)ViewBag.ListName;
    string hn = ln + ".Index";
}

<tr>
    <td>        
        <input type="hidden" name="@hn" value="@index" />
        @Html.LabelFor(model => model.FormFieldID)
    </td>
    <td>
        @Html.DropDownList(ln + "[" + index + "].FormFieldID", 
            new SelectList(new myPrj.Models.DbContext().FormFields, "ID", "FieldName"))
    </td>        
    <td>
        <input type="button" onclick="$(this).parent().parent().remove();" 
            value="Remove" />
    </td>
</tr>

By calling this partial view in the create place view ajaxly, you can render some elements for each tag. Each line of elements contains a label, a DropDownList containing tags, and a remove button to simply remove the created elements.

In the create place view, you have a bare table which will contain those elements you create through the partial view:

<fieldset>
     <legend>Form and FormFields</legend>
     @Html.ValidationMessageFor(model => model.FormFields)</label>
     <table id="tblFields"></table>                                        
     <input type="button" id="btnAddTag" value="Add new Field"/>
     <img id="imgSpinnerl" src="~/Images/indicator-blue.gif" style="display:none;" />
</fieldset>

and you have the following script to create a line of elements for each tag:

$(document).ready(function () {
    $("#btnAddField").click(function () {
        $.ajax({
            url: "/Controller/GetFormFieldRow/FormFields",
            type: 'GET', dataType: 'json',
            success: function (data, textStatus, jqXHR) {
                $("#tblFields").append(jqXHR.responseText);
            },
            error: function (jqXHR, textStatus, errorThrown) {
                $("#tblFields").append(jqXHR.responseText);
            },
            beforeSend: function () { $("#imgSpinnerl").show(); },
            complete: function () { $("#imgSpinnerl").hide(); }
        });
    });
});

The action method GetFormFieldRow is like the following:

public PartialViewResult GetFormFieldRow(string id = "")
    {            
        ViewBag.ListName = id;
        return PartialView("_FormFieldPartial");
    }

and your done for the create... The whole solution for your question has many codes for views, partial views, controllers, ajax calls and model binding. I tried to just show you the way because I really can't to post all of them in this answer.

Here is the full info and how-to.

Hope that this answer be useful and lead the way for you.