I have a ViewModel that contains two lists of Conditions - BuyConditions
and SellConditions
- plus a couple of attributes - Name
etc. The lists are displayed using tables in the view. jQuery allows the tables to be manipulated - adding Conditions to the tables (from Buy Condition / Sell Condition DDL's) and removing Conditions from the tables.
My problem is that the Condition lists are not updated in the model when it is posted back to the controller.
Something that may point to the issue is that when the model is returned to the view (due to ModelStat.IsValid
failing), the data in the Markets DDL still exists, but the data in the Buy and Sell Condition DDL's doesn't.
I can't find an example on the net to figure out where I'm going wrong. Anyone done this before?
ViewModel:
public class StrategyViewModel
{
public string ID { get; set; }
[Display(Name = "Strategy Name")]
public string StrategyName { get; set; }
[Display(Name = "Market")]
public string SelectedMarketID { get; set; }
[Display(Name = "Asset Type")]
public string SelectedShareTypeID { get; set; }
[Display(Name = "Share")]
public string SelectedShareID { get; set; }
public bool Active { get; set; }
public IEnumerable<Market> Markets { get; set; } // top level of three cascading DDL's - the other two populated via JQuery
public IEnumerable<Condition> BuyConditionList { get; set; } // used to populate DDL
public IEnumerable<Condition> SellConditionList { get; set; } // used to populate DDL
public List<BuyCondition> BuyConditions { get; set; } // list of Buy Conditions
public List<SellCondition> SellConditions { get; set; } // list of Sell Conditions
AppRepository repository = new AppRepository();
public StrategyViewModel()
{
// populate lists
Markets = ListUtils.AddDefaultOptionToMarketList( repository.GetMarkets(), -1, "Select a Market" ).AsEnumerable();
BuyConditionList = repository.GetConditions();
SellConditionList = repository.GetConditions();
BuyConditions = new List<BuyCondition>();
SellConditions = new List<SellCondition>();
}
}
Controller:
public ActionResult Create()
{
StrategyViewModel model = new StrategyViewModel();
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "User, Admin")]
public ActionResult Create(StrategyViewModel model)
{
// for debugging
var errorList = ModelState.Values.SelectMany(v => v.Errors);
if (ModelState.IsValid)
{
var user = repository.GetCurrentUser(User.Identity.GetUserId());
var strategy = new Strategy();
strategy.StrategyName = model.StrategyName;
strategy.ShareID = Convert.ToInt32(model.SelectedShareID);
strategy.BuyConditions = model.BuyConditions.ToList();
strategy.SellConditions = model.SellConditions.ToList();
strategy.Active = model.Active;
int strategyID = repository.AddStrategy(user, strategy);
return RedirectToAction("Index");
}
return View(model);
}
View:
@model ShareTrigger.Models.StrategyViewModel
@{
ViewBag.Title = "Create";
}
@{Html.EnableUnobtrusiveJavaScript(true);}
<script type="text/javascript">
$(document).ready(function () {
$(function () {
$('#SelectedMarketID').change(function () {
var selectedMarketID = $(this).val();
$.getJSON('@Url.Action("ShareTypes")', { marketId: selectedMarketID }, function (shareTypes) {
var shareTypesSelect = $('#SelectedShareTypeID');
shareTypesSelect.empty();
$.each(shareTypes, function (index, shareType) {
shareTypesSelect.append($('<option/>').attr('value', shareType.Value).text(shareType.Text));
});
$('#SelectedShareTypeID').trigger("change");
});
});
$('#SelectedShareTypeID').change(function () {
var selectedShareTypeId = $(this).val();
var selectedMarketID = $('#SelectedMarketID').val();
$.getJSON('@Url.Action("Shares")', { shareTypeId: selectedShareTypeId, marketID: selectedMarketID }, function (shares) {
var sharesSelect = $('#SelectedShareID');
sharesSelect.empty();
$.each(shares, function (index, share) {
sharesSelect.append($('<option/>').attr('value', share.Value).text(share.Text));
});
});
});
});
$('#AddBuyCondition').click(function () {
var conditionID = +$("#BuyConditionList").val();
if (conditionID != -2) {
conditionID = +$("#BuyConditionList").val();
var conditionText = $("#BuyConditionList").find('option:selected').text();
$("tr:contains('Add')").remove();
$('#BCDropDownRow').before('<tr class="bcrow"><td class="BuyConditionCell" data-conditionID="' + conditionID + '"><input data-val="true" data-val-number="The field ConditionID must be a number." data-val-required="The ConditionID field is required." id="item_ConditionID" name="item.ConditionID" type="hidden" value="' + conditionID + '">' + conditionText + '</td><td><button class="RemoveBuyCondition type="button" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-minus"></span></button></td></tr>');
$("#BuyConditionList").find('option:selected').remove();
var rows = $('#BuyConditionList option').size();
if (rows == 0) { $("#BuyConditionList").append('<option value="-2">No more conditions to select</option>'); }
}
});
$('#BuyConditionsTable').on('click', '.RemoveBuyCondition', function () {
var conditionID = +$(this).parent().parent().find('.BuyConditionCell').data('conditionid');
var conditionText = $(this).parent().parent().find('.BuyConditionCell').text();
$(this).closest('.bcrow').remove();
$('#BuyConditionList option[value="-2"]').remove();
$("#BuyConditionList").append('<option value="' + conditionID + '">' + conditionText + '</option>');
var options = $('#BuyConditionList option');
var arr = options.map(function (_, o) { return { t: $(o).text(), v: o.value }; }).get();
arr.sort(function (o1, o2) { return o1.t > o2.t ? 1 : o1.t < o2.t ? -1 : 0; });
options.each(function (i, o) {
o.value = arr[i].v;
$(o).text(arr[i].t);
});
});
$('#AddSellCondition').click(function () {
var conditionID = +$("#SellConditionList").val();
if (conditionID != -2) {
conditionID = +$("#SellConditionList").val();
var conditionText = $("#SellConditionList").find('option:selected').text();
$("tr:contains('Add')").remove();
$('#SCDropDownRow').before('<tr class="scrow"><td class="SellConditionCell" data-conditionID="' + conditionID + '"><input id="item_ConditionID" name="item.ConditionID" type="hidden" value="' + conditionID + '">' + conditionText + '</td><td><button class="RemoveSellCondition type="button" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-minus"></span></button></td></tr>');
$("#SellConditionList").find('option:selected').remove();
var rows = $('#SellConditionList option').size();
if (rows == 0) { $("#SellConditionList").append('<option value="-2">No more conditions to select</option>'); }
}
});
$('#SellConditionsTable').on('click', '.RemoveSellCondition', function () {
var conditionID = +$(this).parent().parent().find('.SellConditionCell').data('conditionid');
var conditionText = $(this).parent().parent().find('.SellConditionCell').text();
$(this).closest('.scrow').remove();
$('#SellConditionList option[value="-2"]').remove();
$("#SellConditionList").append('<option value="' + conditionID + '">' + conditionText + '</option>');
var options = $('#SellConditionList option');
var arr = options.map(function (_, o) { return { t: $(o).text(), v: o.value }; }).get();
arr.sort(function (o1, o2) { return o1.t > o2.t ? 1 : o1.t < o2.t ? -1 : 0; });
options.each(function (i, o) {
o.value = arr[i].v;
$(o).text(arr[i].t);
});
});
});
</script>
<h2>Create Strategy</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
@Html.ValidationSummary(true)
<div class="form-group">
@Html.LabelFor(model => model.StrategyName, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.StrategyName)
@Html.ValidationMessageFor(model => model.StrategyName)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.SelectedMarketID, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(x => x.SelectedMarketID, new SelectList(Model.Markets, "MarketId", "MarketCode"))
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.SelectedShareTypeID, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(x => x.SelectedShareTypeID, Enumerable.Empty<SelectListItem>(), "Select an Asset Type")
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.SelectedMarketID, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(x => x.SelectedShareID, Enumerable.Empty<SelectListItem>(), "Select a Share")
</div>
</div>
<div>
</div>
<div class="col-md-10">
<table class="table" id="BuyConditionsTable">
<tr>
<th>
@Html.DisplayName("Buy Conditions")
</th>
<th></th>
</tr>
@foreach (var item in Model.BuyConditions)
{
<tr>
<td>
@Html.HiddenFor(modelItem => item.ConditionID)
@Html.DisplayFor(modelItem => item.ConditionName)
</td>
<td></td>
</tr>
}
<tr id="BCDropDownRow">
<td>
<button id="AddBuyCondition" type="button" class="btn btn-default btn-xs">
<span class="glyphicon glyphicon-plus"></span>
</button>
@Html.DropDownListFor(x => x.BuyConditionList, new SelectList(Model.BuyConditionList, "ConditionID", "ConditionName"))
</td>
<td></td>
</tr>
</table>
</div>
<div>
</div>
<div class="col-md-10">
<table class="table" id="SellConditionsTable">
<tr>
<th>
@Html.DisplayName("Sell Conditions")
</th>
<th></th>
</tr>
@foreach (var item in Model.SellConditions)
{
<tr>
<td>
@Html.HiddenFor(modelItem => item.ConditionID)
@Html.DisplayFor(modelItem => item.ConditionName)
</td>
<td></td>
</tr>
}
<tr id="SCDropDownRow">
<td>
<button id="AddSellCondition" type="button" class="btn btn-default btn-xs">
<span class="glyphicon glyphicon-plus"></span>
</button>
@Html.DropDownListFor(x => x.SellConditionList, new SelectList(Model.SellConditionList, "ConditionID", "ConditionName"))
</td>
<td></td>
</tr>
</table>
</div>
<div class="form-group">
<div class="col-md-offset-9 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
@section Scripts {
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryval")
}