Note that there are some explanatory texts on larger screens.

plurals
  1. POSave button does not fire code behind event
    primarykey
    data
    text
    <p>This is a simple MVC4 application with single drop down list. When I click the Save button an event for SaveWorkRequestDetails should fire, seen below as the [HttpPost], but it doesn't for some reason. I am seeing a 500 error stating that myMachine:12345/WorkRequest/SaveWorkRequestDetails cannot be found when I hit the Save button. See bottom of post for picture of Console window in Chrome.</p> <p>Controller</p> <pre><code>public ActionResult Index() { _model = new WorkRequestViewModel() { WorkSections = GetWorkSections() }; return View(_model); } [HttpPost] public JsonResult SaveWorkRequestDetails(WorkRequestViewModel viewModel) { // TODO: Save logic goes here return Json(new {}); } </code></pre> <p>This is the WorkRequest.js file which should fire the Save event. This does trigger said event IF I DON'T include _model in the return of the View from the ActionResult Index() above, however, the alert seen in the WorkRequest.js file in beforeSend has 'null' when I do that. Clearly, this isn't the intended behavior because I do want those values and the Save button to trigger the [HttpPost]. </p> <pre><code>var WorkRequest = { PrepareKo: function () { ko.bindingHandlers.date = { init: function (element, valueAccessor, allBindingsAccessor, viewModel) { element.onchange = function () { var observable = valueAccessor(); observable(new Date(element.value)); } }, update: function (element, valueAccessor, allBindingsAccessor, viewModel) { var observable = valueAccessor(); var valueUnwrapped = ko.utils.unwrapObservable(observable); if ((typeof valueUnwrapped == 'string' || valueUnwrapped instanceof String) &amp;&amp; valueUnwrapped.indexOf('/Date') === 0) { var parsedDate = WorkRequest.ParseJsonDate(valueUnwrapped); element.value = parsedDate.getMonth() + 1 + "/" + parsedDate.getDate() + "/" + parsedDate.getFullYear(); observable(parsedDate); } } }; }, ParseJsonDate: function (jsonDate) { return new Date(parseInt(jsonDate.substr(6))); }, BindUIwithViewModel: function (viewModel) { ko.applyBindings(viewModel); }, EvaluateJqueryUI: function () { $('.dateInput').datepicker(); }, RegisterUIEventHandlers: function () { $('#Save').click(function (e) { // Check whether the form is valid. Note: Remove this check, if you are not using HTML5 if (document.forms[0].checkValidity()) { e.preventDefault(); $.ajax({ type: "POST", url: WorkRequest.SaveUrl, data: ko.toJSON(WorkRequest.ViewModel), contentType: 'application/json', async: true, beforeSend: function () { // Display loading image alert(ko.toJSON(WorkRequest.ViewModel)); }, success: function (result) { // Handle the response here. }, complete: function () { // Hide loading image. }, error: function (jqXHR, textStatus, errorThrown) { // Handle error. } }); } }); }, }; $(document).ready(function () { WorkRequest.PrepareKo(); WorkRequest.BindUIwithViewModel(WorkRequest.ViewModel); WorkRequest.EvaluateJqueryUI(); WorkRequest.RegisterUIEventHandlers(); }); </code></pre> <p>This is my Index.cshtml</p> <pre><code>@using Microsoft.Ajax.Utilities @model WorkRequest.ViewModel.WorkRequestViewModel @using WorkRequest.Helper @section styles{ @Styles.Render("~/Content/themes/base/css") &lt;link href="~/Content/WorkRequest.css" rel="stylesheet" /&gt; } @section scripts{ @Scripts.Render("~/bundles/jqueryui") &lt;script src="~/Scripts/knockout-2.2.0.js"&gt;&lt;/script&gt; &lt;script src="~/Scripts/knockout.mapping-latest.js"&gt;&lt;/script&gt; &lt;script src="~/Scripts/Application/WorkRequest.js"&gt;&lt;/script&gt; &lt;script type="text/javascript"&gt; WorkRequest.SaveUrl = '@Url.Action("SaveWorkRequestDetails", "WorkRequest")'; WorkRequest.ViewModel = ko.mapping.fromJS(@Html.Raw(Json.Encode(Model))); &lt;/script&gt; } &lt;form&gt; &lt;div class="mainWrapper"&gt; &lt;table&gt; &lt;tr&gt; &lt;td&gt;Work Sections: &lt;/td&gt; &lt;td&gt; @Html.DropDownList("WorkSections", Model.WorkSections, new {data_bind="value: WorkRequest.ViewModel.WorkSections"}) &lt;/td&gt; &lt;/tr&gt; &lt;/table&gt; &lt;/div&gt; &lt;br /&gt; &lt;input id="Save" type="submit" value="Save" /&gt; &lt;/form&gt; </code></pre> <p>Now this entire sample is based on a demo I found here: <a href="http://www.codeproject.com/Articles/657981/ASP-NET-MVC-4-with-Knockout-Js" rel="nofollow noreferrer">http://www.codeproject.com/Articles/657981/ASP-NET-MVC-4-with-Knockout-Js</a> and I was able to get it working just fine until I tried to populate my own drop down list. I am able to get the data for my drop down list and populate my SelectList without issues and see it on my form.</p> <p>My View Model</p> <pre><code>public class WorkRequestViewModel { public SelectList WorkSections { get; set; } } </code></pre> <p>I have confirmed this SelectList is populated and I see the items in my drop down list but the demo application uses two custom classes to make the controls in the Index.cshtml page observable so that knockout can do its thing...</p> <p>HtmlExtensions.cs</p> <pre><code>public static class HtmlExtensions { /// &lt;summary&gt; /// To create an observable HTML Control. /// &lt;/summary&gt; /// &lt;typeparam name="TModel"&gt;The model object&lt;/typeparam&gt; /// &lt;typeparam name="TProperty"&gt;The property name&lt;/typeparam&gt; /// &lt;param name="htmlHelper"&gt;The &lt;see cref="HtmlHelper&lt;T&gt;"/&gt;&lt;/param&gt; /// &lt;param name="expression"&gt;The property expression&lt;/param&gt; /// &lt;param name="controlType"&gt;The &lt;see cref="ControlTypeConstants"/&gt;&lt;/param&gt; /// &lt;param name="htmlAttributes"&gt;The html attributes&lt;/param&gt; /// &lt;returns&gt;Returns computed HTML string.&lt;/returns&gt; public static IHtmlString ObservableControlFor&lt;TModel, TProperty&gt;( this HtmlHelper&lt;TModel&gt; htmlHelper, Expression&lt;Func&lt;TModel, TProperty&gt;&gt; expression, string controlType = ControlTypeConstants.TextBox, object htmlAttributes = null) { var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData); string jsObjectName = null; string generalWidth = null; // This will be useful, if the same extension has // to share with multiple pages (i.e. each with different view models). switch (metaData.ContainerType.Name) { case "WorkRequestViewModel": // Where WorkRequest is the Javascript object name (namespace in theory). jsObjectName = "WorkRequest.ViewModel."; generalWidth = "width: 380px"; break; default: throw new Exception(string.Format("The container type {0} is not supported yet.", metaData.ContainerType.Name)); } var propertyObject = jsObjectName + metaData.PropertyName; TagBuilder controlBuilder = null; // Various control type creation. switch (controlType) { case ControlTypeConstants.TextBox: controlBuilder = new TagBuilder("input"); controlBuilder.Attributes.Add("type", "text"); controlBuilder.Attributes.Add("style", generalWidth); break; case ControlTypeConstants.Html5NumberInput: controlBuilder = new TagBuilder("input"); controlBuilder.Attributes.Add("type", "number"); controlBuilder.Attributes.Add("style", generalWidth); break; case ControlTypeConstants.Html5UrlInput: controlBuilder = new TagBuilder("input"); controlBuilder.Attributes.Add("type", "url"); controlBuilder.Attributes.Add("style", generalWidth); break; case ControlTypeConstants.TextArea: controlBuilder = new TagBuilder("textarea"); controlBuilder.Attributes.Add("rows", "5"); break; case ControlTypeConstants.DropDownList: controlBuilder = new TagBuilder("div"); controlBuilder.Attributes.Add("class", "dropDownList"); break; case ControlTypeConstants.JqueryUIDateInput: controlBuilder = new TagBuilder("input"); controlBuilder.Attributes.Add("type", "text"); controlBuilder.Attributes.Add("style", generalWidth); controlBuilder.Attributes.Add("class", "dateInput"); controlBuilder.Attributes.Add("data-bind", "date: " + propertyObject); // date is the customized knockout binding handler. Check PrepareKo method of Person. break; default: throw new Exception(string.Format("The control type {0} is not supported yet.", controlType)); } controlBuilder.Attributes.Add("id", metaData.PropertyName); controlBuilder.Attributes.Add("name", metaData.PropertyName); // Check data-bind already exists, add if not. if (!controlBuilder.Attributes.ContainsKey("data-bind")) { controlBuilder.Attributes.Add("data-bind", "value: " + propertyObject); } // Merge provided custom html attributes. This overrides the previously defined attributes, if any. if (htmlAttributes != null) { controlBuilder.MergeAttributes( HtmlExtensions.AnonymousObjectToHtmlAttributes(htmlAttributes), true); } return MvcHtmlString.Create(controlBuilder.ToString()); } /// &lt;summary&gt; /// To convert '_' into '-'. /// &lt;/summary&gt; /// &lt;param name="htmlAttributes"&gt;The html attributes.&lt;/param&gt; /// &lt;returns&gt;Returns converted &lt;see cref="RouteValueDictionary"/&gt;.&lt;/returns&gt; private static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes) { RouteValueDictionary result = new RouteValueDictionary(); if (htmlAttributes != null) { foreach (System.ComponentModel.PropertyDescriptor property in System.ComponentModel.TypeDescriptor.GetProperties(htmlAttributes)) { result.Add(property.Name.Replace('_', '-'), property.GetValue(htmlAttributes)); } } return result; } } </code></pre> <p>ViewModelConstants.cs </p> <pre><code>public static class ControlTypeConstants { public const string TextBox = "TextBox"; public const string TextArea = "TextArea"; public const string CheckBox = "CheckBox"; public const string DropDownList = "DropDownList"; public const string Html5NumberInput = "Html5NumberInput"; public const string Html5UrlInput = "Html5UrlInput"; public const string Html5DateInput = "Html5DateInput"; public const string JqueryUIDateInput = "JqueryUIDateInput"; } </code></pre> <p>So the root of my issue is that once the UI is successfully populated with a drop down list containing values, I select one and hit the save button and I see the alert display what is in the model but the Save button is never triggered.</p> <p>In the example provided from CodeProject a control has this custom ObservableControlFor after @Html, see above class HtmlExtensions. This is what it looks like: @Html.ObservableControlFor(model => model.Name, ControlTypeConstants.TextBox) so I tried to do the same in my example but I don't know how to turn @Html.DropDownList("myList") into @Html.ObservableControlFor... I tried the following @Html.ObservableControlFor(model => model.WorkSections, ControlTypeConstants.DropDownList) but the control is never rendered to the UI and I get the same 500 error as before.</p> <p><img src="https://i.stack.imgur.com/VpM7O.jpg" alt="Console from Chrome"></p> <p>I have tried ViewData in my @Html control but the function is expecting something like model => model.WorkSections</p> <p><img src="https://i.stack.imgur.com/VHyUf.jpg" alt="Error"></p> <p>HTML Markup from View Source</p> <pre><code>&lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;meta charset="utf-8" /&gt; &lt;meta name="viewport" content="width=device-width" /&gt; &lt;title&gt;&lt;/title&gt; &lt;link href="/Content/site.css" rel="stylesheet"/&gt; &lt;script src="/Scripts/modernizr-2.6.2.js"&gt;&lt;/script&gt; &lt;link href="/Content/themes/base/jquery.ui.core.css" rel="stylesheet"/&gt; &lt;link href="/Content/themes/base/jquery.ui.resizable.css" rel="stylesheet"/&gt; &lt;link href="/Content/themes/base/jquery.ui.selectable.css" rel="stylesheet"/&gt; &lt;link href="/Content/themes/base/jquery.ui.accordion.css" rel="stylesheet"/&gt; &lt;link href="/Content/themes/base/jquery.ui.autocomplete.css" rel="stylesheet"/&gt; &lt;link href="/Content/themes/base/jquery.ui.button.css" rel="stylesheet"/&gt; &lt;link href="/Content/themes/base/jquery.ui.dialog.css" rel="stylesheet"/&gt; &lt;link href="/Content/themes/base/jquery.ui.slider.css" rel="stylesheet"/&gt; &lt;link href="/Content/themes/base/jquery.ui.tabs.css" rel="stylesheet"/&gt; &lt;link href="/Content/themes/base/jquery.ui.datepicker.css" rel="stylesheet"/&gt; &lt;link href="/Content/themes/base/jquery.ui.progressbar.css" rel="stylesheet"/&gt; &lt;link href="/Content/themes/base/jquery.ui.theme.css" rel="stylesheet"/&gt; &lt;link href="/Content/WorkRequest.css" rel="stylesheet" /&gt; &lt;/head&gt; &lt;body&gt; &lt;form&gt; &lt;div class="mainWrapper"&gt; &lt;table&gt; &lt;tr&gt; &lt;td&gt;Work Sections: &lt;/td&gt; &lt;td&gt; &lt;div class="dropDownList" data-bind="value: WorkRequest.ViewModel.WorkSections" id="WorkSections" name="WorkSections"&gt;&lt;/div&gt; &lt;/td&gt; &lt;/tr&gt; &lt;/table&gt; &lt;/div&gt; &lt;br /&gt; &lt;input id="Save" type="submit" value="Save" /&gt; &lt;/form&gt; &lt;script src="/Scripts/jquery-1.8.2.js"&gt;&lt;/script&gt; &lt;script src="/Scripts/jquery-ui-1.8.24.js"&gt;&lt;/script&gt; &lt;script src="/Scripts/knockout-2.2.0.js"&gt;&lt;/script&gt; &lt;script src="/Scripts/knockout.mapping-latest.js"&gt;&lt;/script&gt; &lt;script src="/Scripts/Application/WorkRequest.js"&gt;&lt;/script&gt; &lt;script type="text/javascript"&gt; WorkRequest.SaveUrl = '/WorkRequest/SaveWorkRequestDetails'; WorkRequest.ViewModel = ko.mapping.fromJS({"WorkSectionId":0,"WorkSections":[{"Selected":false,"Text":"308:IPACS","Value":"308"},{"Selected":false,"Text":"312:IPACS","Value":"312"},{"Selected":false,"Text":"301:IPACS","Value":"301"},{"Selected":false,"Text":"316:IPACS","Value":"316"},{"Selected":false,"Text":"307:IPACS","Value":"307"},{"Selected":false,"Text":"318:IPACS","Value":"318"},{"Selected":false,"Text":"313:IPACS","Value":"313"},{"Selected":false,"Text":"319:IPACS","Value":"319"},{"Selected":false,"Text":"315:IPACS","Value":"315"},{"Selected":false,"Text":"310:IPACS","Value":"310"},{"Selected":false,"Text":"300:IPACS","Value":"300"},{"Selected":false,"Text":"302:IPACS","Value":"302"},{"Selected":false,"Text":"304:IPACS","Value":"304"},{"Selected":false,"Text":"306:IPACS","Value":"306"},{"Selected":false,"Text":"309:IPACS","Value":"309"},{"Selected":false,"Text":"305:STORM","Value":"305"},{"Selected":false,"Text":"311:IPACS","Value":"311"},{"Selected":false,"Text":"317:IPACS","Value":"317"},{"Selected":false,"Text":"303:IPACS","Value":"303"},{"Selected":false,"Text":"314:IPACS","Value":"314"}]}); &lt;/script&gt; &lt;/body&gt; &lt;/html&gt; </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload