Create new parent and child on the same page
Asked Answered
A

5

13

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.

Attribution answered 27/1, 2013 at 15:8 Comment(4)
Could you show the code you have so far? Explanations are much better when garnished with concrete code examples.Ave
One problem i have noticed is that the partial view contains a form. I have removed it, but it doesn't changes the problem.Attribution
Someone? It should be a very common scenario.Attribution
progware.org/Blog/post/…Rociorock
J
1

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.

Janot answered 27/7, 2013 at 12:42 Comment(0)
M
1

You can use @Html.RenderPartial(_CreateFormField.cshtml) htlper method inside your parent cshtml page.

For mode info, http://msdn.microsoft.com/en-us/library/dd492962(v=vs.118).aspx

I am providing an pseudo code example,

@Foreach(childModel in model.FormFields)
{
    @Html.RenderPartial("childView.cshtml","Name", childModel)
}

Please try it in proper c# syntactical way and you will get your partial views rendered for each collection item.

Mcloughlin answered 15/12, 2014 at 12:41 Comment(0)
A
0

The problem is that your partial view needs to use:

@model Form //Instead of FormField

and then inside of the partial view you must use model => model.FormField.x

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

This only works for one-to-one relationships (or so I read in this thread: Here). It also does not matter if you have @Html.Form inside of the partial view.

After making those changes I had no problem getting all of the posted data back to the controller.

EDIT: I've run into problems with this as anyone would soon figure out.

The better way to do this (that I've seen) is instead use an EditorTemplate. This way the editor stays independent of the Parent, and you can add a Form on any page, not simply a page where you are also adding a FormField.

You would then replace

@Html.Partial("view")

with

@Html.EditorFor()

Basically, I've found out that it's far better to use an @Html.EditorFor instead of a partial view when doing editing (It is called a view - not an editor).

Approbate answered 6/6, 2013 at 17:22 Comment(0)
S
0

If you want to use grid based layout you may want to try Kendo UI grid

Shizukoshizuoka answered 7/1, 2015 at 16:49 Comment(0)
R
0

Use jQueryto add FormField on click of button.

Similar I've used it as follows

 <div class="accomp-multi-field-wrapper">
 <table class="table">
    <thead>
        <tr>
            <th>Name</th>
            <th>FormId</th>
            <th>Remove</th>
           </tr>
     </thead>
<tbody class="accomp-multi-fields">
 <tr class="multi-field">
        <td> <input name="FormFields[0].Name" type="text" value=""></td>
        <td> <input name="FormFields[0].FormId" type="text" value=""></td>
        <td> <button type="button" class="remove-field">X</button></td>
     </tr> 

</tbody>
<tr>
    <th><button type="button" class="add-field btn btn-xs btn-primary addclr"><i class="fa fa-plus"></i> Add field</button> </th>
</tr>
</table>
</div>

and jQuery is as follows for adding field and removing

 <script>
 $('.accomp-multi-field-wrapper').each(function () {
          var $wrapper = $('.accomp-multi-fields', this);
          $(".add-field", $(this)).click(function (e) {
          var $len = $('.multi-field', $wrapper).length;
          $('.multi-field:first-child', $wrapper).clone(false, true).appendTo($wrapper).find('select,input,span').val('').each(function () {
          $(this).attr('name', $(this).attr('name').replace('\[0\]', '\[' + $len + '\]'));
    });

$(document).on("click",'.multi-field .remove-field', function () {
            if ($('.multi-field', $wrapper).length > 1) {
                $(this).closest('tr').remove();
                //
                $('.multi-field', $wrapper).each(function (indx) {
                    $(this).find('input,select,span').each(function () {
                      $(this).attr('name', $(this).attr('name').replace(/\d+/g, indx));

                    })
                })
            }
        });
    </script>

Hope so this is going to help you.

Richerson answered 7/2, 2015 at 11:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.