This is a migrated thread and some comments may be shown as answers.

[Solved] MVC Grid within a form post?

0 Answers 98 Views
Grid
This is a migrated thread and some comments may be shown as answers.
This question is locked. New answers and comments are not allowed.
Nick
Top achievements
Rank 1
Nick asked on 21 Aug 2012, 03:39 PM
Hi,

I've recently created a new MVC project and wanted to house a Telerik MVC grid WITHIN a form.  The idea was that the user could edit several standard attributes (name, type, etc), but also add, remove and edit rows within the grid.  When the user clicks the form submit button, the MVC model binder would ideally resolve the form inputs and serialize the values in the grid into the model type defined in the view, and save the ENTIRE object (including the changes made to the underlying grid object) at that point.

I tried many different approaches to achieving this, but was eventually forced into a nasty AJAX serialization and post solution, using jquery.  Surely this should be MUCH easier to achieve?...I find it hard to believe that wanting a grid within a form is such a rarity!

My code:
----------

VIEW:

@model X.Models.BaseObjectViewModel
 
@using (Html.BeginForm("Edit", "BaseObject", FormMethod.Post, new { id = "baseObjectForm" }))
{
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>Details</legend>
        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.TextBoxFor(o => o.Name)
        </div>
        @Html.ValidationMessageFor(model => model.Name)
        <div class="editor-label">
            @Html.LabelFor(model => model.ID)
        </div>
        @Html.DisplayText("ID")
        @Html.HiddenFor(model => model.ID)
        <div class="editor-label">
            @Html.LabelFor(model => model.ExternalID, "ExternalID")
        </div>
        @Html.DisplayText("ExternalID")
        @Html.HiddenFor(model => model.ExternalID)
        <div class="editor-label">
            @Html.LabelFor(model => model.Type, "model type")
        </div>
        @Html.DisplayText("Type")
        @Html.HiddenFor(model => model.Type)
    </fieldset>
     
    Html.Telerik().Grid(Model.ChildObject)
            .Name("ChildObjects")
            .Sortable()
            .Pageable()
            .KeyboardNavigation()
            .DataKeys(keys => keys.Add(key => key.ID))
            .Columns(column =>
            {
                column.Bound("ID").Sortable(false).ReadOnly().Width("10%");
                column.Bound("Name").Sortable(true).Width("80%");
                column.Command(command =>
                    {
                        command.Delete();
                    }).Width("10%");
            })
            .DataBinding(dataBinding => dataBinding.Ajax()
                .Delete<X.Controllers.BaseObjectController>(c => c.GridDelete(0))
            ).Render();
    <p>
        <input type="submit" value="Save" onclick="PostForm()" />
    </p>
}
 
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
 
<script type="text/javascript">
 
    $('#baseObjectForm').submit(function (e) {
        e.preventDefault();
    });
 
    function PostForm() {
        var gridData = $('#ChildObjects').data('tGrid').data;
        var serialisedGridData = JSON.stringify(gridData);
        var serialisedFormData = JSON.stringify(serializeForm($('#baseObjectForm')));
        $.ajax({
            url: "/BaseObject/Edit",
            type: "POST",
            dataType: "application/JSON",
            data: { formCollectionSerializedString: serialisedFormData, childObjectViewModelSerializedString: serialisedGridData }
        });
    };
 
</script>

CONTROLLER:

namespace X.Controllers
{
    public class BaseObjectController : Controller
    {
        public ActionResult Index()
        {
            //Not relevant!
        }
 
        [GridAction]
        public ActionResult GridDelete(byte id)
        {
            BaseObject.DeleteBaseObject(id);
            return RedirectToAction("Index");
        }
 
        [HttpGet]
        public ActionResult Edit(BaseObjectViewModel viewModel)
        {
            BaseObjectViewModel viewModel = BaseObject.GetModel(viewModel.ID);
            AutoMapper.Mapper.CreateMap<BaseObjectChild, ChildViewModel>();
            viewModel.Child = AutoMapper.Mapper.Map<BaseObjectChildList, List<ChildViewModel>>(viewModel.Child);
            return View("Edit", viewModel);
        }
 
        [HttpPost]
        public ActionResult Edit(string formCollectionSerializedString, string childViewModelSerializedString)
        {
            List<BaseObjectChildViewModel>childList;
            BaseObjectViewModel viewModel;
            try
            {
                viewModel = new JavaScriptSerializer().Deserialize<BaseObjectViewModel>(formCollectionSerializedString);
            }
            catch
            {
                throw new ArgumentException(String.Format("Unable to deserialise base object view-model due to incorrectly serialised form - {0}", formCollectionSerializedString));
            }
            try
            {
                childList = new JavaScriptSerializer().Deserialize<List<BaseObjectChildViewModel>>(childViewModelSerializedString);
            }
            catch
            {
                throw new ArgumentException(String.Format("Unable to deserialise child list due to malformed JSON string - {0}", childViewModelSerializedString));
            }
            if (ModelState.IsValid)
            {
                BaseObject updatedObject = BaseObject.GetModel(viewModel.ID);
                updatedObject.Name = viewModel.Name;
                updatedObject.ChildList = childList ;
 
                updatedObject.Save();
                return RedirectToAction("Index", "BaseObject");
            }
            //Validation failed - return to View to dsiplay error.
            return View(viewModel);
        }
    }
}

(Sorry for the terribly named classes - I had to change them for security reasons).


Now, this works, but is there a better way of doing it?  I'd MUCH prefer to use Server binding, with a controller action that takes in a FormCollection parameter and a ChildObject strongly typed parameter, but I gave up after trying MANY different combinations of this.

I also now have the annoying problem of the grid's built in functionality (in this case refresh and delete) not working, because it hits this controller action:

public ActionResult Edit(string formCollectionSerializedString, string childViewModelSerializedString)

This obviously causes an error, since the call to this action is not done through the same jquery/ajax post as the button click, so these parameters are null.


Am I missing something obvious, or is it REALLY this hard to do this sort of thing?!

Thanks.


Nick
Tags
Grid
Asked by
Nick
Top achievements
Rank 1
Share this question
or