Reputation: 15237
I am building a simple web application where users can vote on topics that the admins set. Each topic, can have two or more options, for example a Yes or No style vote topic, will have two options, where as a voting topic of "What is the best restaurant in Kensington?" can have many options.
Here is my current ViewModel:
public class NewTopicVM
{
[Required]
[Display(Name = "Topic Title")]
public string TopicTitle { get; set; }
[Required]
[Display(Name = "Topic Description")]
public string TopicDescription { get; set; }
[Display(Name = "Topic Image")]
public byte[] TopicImage { get; set; }
public List<NewOptionVM> TopicOptions { get; set; }
}
public class NewOptionVM
{
public string OptTitle { get; set; }
public string OptDescription { get; set; }
}
Note the list of NewOptionVM
in the NewTopicVM
. Thanks Iko's answer, I have managed to bind the non-dynamically generated inputs, however my dynamically generated ones are not getting bound for some reason !
Here's the jQuery code that creates the Option input fields:
var index = 1;
function generateOptMarkup() {
var container = $('<div />');
var optionInputsBlock = $('<div />');
optionInputsBlock.addClass('optionInputsBlock');
var inputTitleBlock = $('<div />');
inputTitleBlock.addClass('inputBlock');
var inputDescBlock = $('<div />');
inputDescBlock.addClass('inputBlock');
var optTitleLbl = $('<label />');
optTitleLbl.attr('for', 'TopicOptions_' + ++index + '__OptTitle');
optTitleLbl.text('Option Title');
var optDescLbl = $('<label />');
optDescLbl.attr('for', 'TopicOptions_' + index + '__OptDescription');
optDescLbl.text('Option Description');
var optTitleInpt = $('<input type="text" />');
optTitleInpt.addClass('text-box single-line');
optTitleInpt.attr('name', 'TopicOptions_' + index + '__OptTitle');
optTitleInpt.attr('id', 'TopicOptions_' + index + '__OptTitle');
optTitleInpt.attr('data-val-required', 'The Option Title field is required.');
optTitleInpt.attr('data-val', 'true');
var optDescInpt = $('<input type="text" />');
optDescInpt.addClass('text-box single-line');
optDescInpt.attr('name', 'TopicOptions_' + index + '__OptDescription');
optDescInpt.attr('id', 'TopicOptions_' + index + '__OptDescription');
optDescInpt.attr('data-val-required', 'The Option Description field is required.');
optDescInpt.attr('data-val', 'true');
inputTitleBlock.append(optTitleLbl).append(optTitleInpt);
inputDescBlock.append(optDescLbl).append(optDescInpt);
optionInputsBlock.append(inputTitleBlock).append(inputDescBlock);
container.append(optionInputsBlock);
return container.html();
}
$('div.buttons > a').click(function () {
$('div.topicOptionsBlock').append(generateOptMarkup());
});
I start at Index = 1
because I already have 2 options displayed. Here is a screenshot:
The first two boxes get bound fine, but the third one which is dynamically added after clicking the [Add Option] button does not!
What am I doing wrong? I have ensured the indexing is correct, yet it isn't working :(
Upvotes: 0
Views: 1899
Reputation: 8289
Loop through the list with a for loop and use the index and editorfor.
// other fields as usual
For(int i = 0; i < Model.TopicOptions.Count; i++)
{
@Html.EditorFor(m => m.TopicOptions[i].OptTitle)
@Html.EditorFor(m => m.TopicOptions[i].OptDescription)
}
MVC default binding will catch them now. If you examine the HTML MVC has some format to bind them so it's like TopicOptions__0_OptTitle.
Put a breakpoint on the action, the model should be bound completely, you don't need to manually do anything.
Edit. You can put the inside of the for loop (the 2 editorFors) in a PartialView and then when you call it pass the index via ViewData.
for(...)
@Html.Partial("_NewOption", new ViewDataDictionary { { "index", i } })
Now, if you add new items dynamically, you can make an ajax call to an action to return a list with the last item being a new object. The action should return the index, and then the binding will be ok as the name and id will be generated automatically. (You can also send everything to the server to delete)
It just depends if you want to handle the id/name fields manually in javascript, or automatically from the server.
Upvotes: 2