In my view model, there is a list of element which contains a table of text inputs.
View model > List of element > Table > lots of text inputs
All of this is generated in the view via EditorTemplates.
Everything is bound perfectly to the view when it load (MVC is creating the right IDs and Names of each input. Now I want to add and delete rows and have the binding updated. This means, that all the right numbers are updated in the IDs and Name.
I have an idea of how to do it with pure JavaScript, but that would be very dirty and a pain to update when things change. I would like to do it with an HTML helpers or a model binder but haven't found a way yet!
Anyone have an idea of how to do that?
EDIT : apparently I have more chance to get answered if I post lots of code detail... but in this complex case, that's going to be long, so hang on!
Here is the view model. (Keep in mind that this code is a dumbed down version)
public class MVCViewModel
{
public List<ActivityElement> ActivityElementList { get; set; }
}
public abstract class ActivityElement
{
}
public class Step : ActivityElement
{
public Step()
{
this.Id = Guid.NewGuid();
this.Label = "Basic Step";
}
public Step(string label, AbstractInput input)
: this()
{
this.Label = label;
this.Input = input;
}
public Guid Id { get; set; }
public string Label { get; set; }
public AbstractInput Input { get; set; }
}
public abstract class AbstractInput
{
public object Value { get; set; }
}
public class GridInput : AbstractInput
{
public List<GridCell> Headerlist { get; set; }
public List<GridRow> RowList { get; set; }
}
public class GridRow
{
public List<GridCell> CellList { get; set; }
}
public class GridCell
{
public string ColumnName { get; set; }
public AbstractInput Input { get; set; }
}
public class TextInput: AbstractInput
{
public TextInput(string value)
{
this.Value = value;
}
}
Alright! If somehow you are still with me, that's good! I have put them in the order that they will be used in this particular case.
To dumb this down, the view model contains a list of Step. The first step contains a GridInput as his AbstractInput property. That grid contains rows and each rows contains cells. For this case, let say each cell contains a TextInput as his AbstractInput.
Now, on the view, when this is displayed with EditorTemplates and HtmlHelpers, all the IDs and Names of the inputs are good (therefor the binding is good), as shown here in this HTML output :
<td>
<input id="ActivityElementList_0__Input_RowList_0__CellList_0__Input_ModelType" name="ActivityElementList[0].Input.RowList[0].CellList[0].Input.ModelType" type="hidden" value="knockout_binding_prototype.Models.TextInput">
<input id="ActivityElementList_0__Input_RowList_0__CellList_0__Input_Value" name="ActivityElementList[0].Input.RowList[0].CellList[0].Input.Value" type="text" value="cell 1 row 1">
</td>
<td>
<input id="ActivityElementList_0__Input_RowList_0__CellList_1__Input_ModelType" name="ActivityElementList[0].Input.RowList[0].CellList[1].Input.ModelType" type="hidden" value="knockout_binding_prototype.Models.TextInput">
<input id="ActivityElementList_0__Input_RowList_0__CellList_1__Input_Value" name="ActivityElementList[0].Input.RowList[0].CellList[1].Input.Value" type="text" value="cell 2 row 1">
</td>
What I am trying to do here, is to be able to add and delete row to this table. This is fairly easy... The hard part is making sure all the binding (IDs and Names) are still good. In the case of this particular HTML output, if I delete the first row, I need the IDs and Names of the second row, to become equivalent to the first row IDs and Name.
I have been trying to find the most generic way to do it, and to try and make it JavaScript less as possible. All ideas will be pondered, so don't afraid to say something!
EDIT 2 : OK, as far as deleting rows go, I decided to just hide the delete row and flag it as deleted, so all the biding remains good. Adding row is simple in JavaScript, but I am searching for a way to do it without JavaScript (or only a little bit).
Here is the update in the view model for the delete flag.
public class GridRow
{
public GridRow()
{
IsDeleted = false;
}
public List<GridCell> CellList { get; set; }
public bool IsDeleted { get; set; }
}
Try this way without buggy JS html modifications. You can do delete this way as well. Also you can decide if you want to save the model on every add/delete call or save it after all changes.
Models:
public class SampleModel
{
public List<ItemModel> Items { get; set; }
public SampleModel()
{
Items = new List<ItemModel>();
}
}
public class ItemModel
{
public int Id { get; set; }
public string Name { get; set; }
}
Controller:
[HttpPost]
public ActionResult Add(SampleModel model)
{
model.Items.Add(new ItemModel { Name = "New Item" });
return PartialView("Items");
}
public ActionResult Index()
{
var model = new SampleModel(); // Or get model
return View(model);
}
[HttpPost]
public ActionResult Index(SampleModel model)
{
// Save model to DB
return RedirectToAction("Index");
}
Index.cshtml
@model SampleModel
@using (Html.BeginForm())
{
<div id="container">
@Html.Partial("Items", Model)
</div>
<a id="add" href="javascript:void(0);">Add</a>
<button type="submit">Save</button>
}
<script src="jquery.js"></script>
<script type="text/javascript">
$(function () {
$('#add').on('click', function () {
$.post('@Url.Action("Add")', $('form').serializeModel(), function(html) {
$('#container').html(html);
});
});
});
$.fn.serializeModel = function () {
var model = {};
$(this).serializeArray().map(function (item) {
model[item.name] = item.value;
});
return model;
};
</script>
Items.cshtml:
@model SampleModel
<table>
@for (var i = 0; i < Model.Items.Count; i++)
{
<tr>
<td>
@Html.HiddenFor(m => Model.Items[i].Id)
@Html.TextBoxFor(m => Model.Items[i].Name)
</td>
</tr>
}
</table>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With