将所选项目从下拉列表传递到viewmodel

时间:2013-11-25 08:06:07

标签: asp.net-mvc-4 knockout.js viewmodel html-select

我在页面上有四个控件,一个带有名字和姓氏的简单表单,出生日期和下拉列表,其中包含一些国家名称。当我对这些控件进行更改时,我能够在我的viewModel中看到那些在下面的SavePersonDetails POST中作为参数传入的更改,但我从未在该视图模型中看到LocationId更新,我不确定原因。

这就是我的标记,Index.cshtml:

@model Mvc4withKnockoutJsWalkThrough.ViewModel.PersonViewModel
@using System.Globalization
@using Mvc4withKnockoutJsWalkThrough.Helper

@section styles{
@Styles.Render("~/Content/themes/base/css")
<link href="~/Content/Person.css" rel="stylesheet" />
}

@section scripts{
@Scripts.Render("~/bundles/jqueryui")
<script src="~/Scripts/knockout-2.1.0.js"></script>
<script src="~/Scripts/knockout.mapping-latest.js"></script>
<script src="~/Scripts/Application/Person.js"></script>
<script type="text/javascript">
    Person.SaveUrl = '@Url.Action("SavePersonDetails", "Person")';
    Person.ViewModel = ko.mapping.fromJS(@Html.Raw(Json.Encode(Model)));
    var userObject =  '@Html.Raw(Json.Encode(Model))';
    var locationsArray =  '@Html.Raw(Json.Encode(Model.Locations))';
    var vm = {
        user : ko.observable(userObject),
        availableLocations: ko.observableArray(locationsArray)
    };
    ko.applyBindings(vm);
</script>
}
<form>
<p data-bind="with: user">
        Your country:
        <select data-bind="options: $root.availableLocations, optionsText: 'Text', optionsValue: 'Value', value: LocationID, optionsCaption: 'Choose...'">
        </select>
    </p>
</form>

这是我的视图模型:

public class PersonViewModel
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string LocationId { get; set; }
    public IEnumerable<SelectListItem> Locations { get; set; } 
}

我有一个简单的控制器加载我的Person和一个包含三个国家的下拉列表。

private PersonViewModel _viewModel;

public ActionResult Index()
{        
    var locations = new[]
    {
        new SelectListItem { Value = "US", Text = "United States" },
        new SelectListItem { Value = "CA", Text = "Canada" },
        new SelectListItem { Value = "MX", Text = "Mexico" },
    };

    _viewModel = new PersonViewModel
    {
        Id = 1,
        FirstName = "Test",
        LastName = "Person",
        DateOfBirth = new DateTime(2000, 11, 12),
        LocationId = "",  // I want this value to get SET when the user changes their selection in the page
        Locations = locations
    };

    _viewModel.Locations = locations;

    return View(_viewModel);
}

[HttpPost]
public JsonResult SavePersonDetails(PersonViewModel viewModel)
{
    int id = -1;

    SqlConnection myConnection = new SqlConnection("server=myMachine;Trusted_Connection=yes;database=test;connection timeout=30");

    try
    {
           // omitted
    }
    catch (Exception e)
    {
        Console.WriteLine(e.ToString());
    }
    finally
    {
        myConnection.Close();
    }
    return Json(id, "json");
}

最后,这是我的Person.js文件,我正在使用knockout

var Person = {

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) && valueUnwrapped.indexOf('/Date') === 0) {
                var parsedDate = Person.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: Person.SaveUrl,
                data: ko.toJSON(Person.ViewModel),
                contentType: 'application/json',
                async: true,
                beforeSend: function () {
                    // Display loading image
                },
                success: function (result) {
                    // Handle the response here.
                    if (result > 0) {
                        alert("Saved");
                    } else {
                        alert("There was an issue");
                    }

                },
                complete: function () {
                    // Hide loading image.
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    // Handle error.
                }
            });
          }
      });
  },
};

$(document).ready(function () {
  Person.PrepareKo();
  Person.BindUIwithViewModel(Person.ViewModel);
  Person.EvaluateJqueryUI();
  Person.RegisterUIEventHandlers();
});

如您所见,我在页面中有数据,但没有一个显示为选中

locations

2 个答案:

答案 0 :(得分:1)

您的解决方案过于复杂,导致您的数据出现某种奇怪现象。而不是试图修补泰坦尼克号,你最好的选择是重新开始并简化:

您网页的模型包含您需要的所有信息。没有必要尝试创建两个完全独立的视图模型来处理用户数据与位置。使用映射插件,您可以为主视图模型中的各种对象指定不同的“视图模型”,并且可以使用更简单的模式来设置所有这些。这就是我要做的事情:

// The following goes in external JS file

var PersonEditor = function () {
    var _init = function (person) {
        var viewModel = PersonEditor.PersonViewModel(person);
        ko.applyBindings(viewModel);
        _wireEvents(viewModel);
    }

    var _wireEvents = function (viewModel) {
        // event handlers here
    }

    return {
        Init: _init
    }
}();

PersonEditor.PersonViewModel = function (person) {
    var mapping = {
        'Locations': {
            create: function (options) {
                return new PersonEditor.LocationViewModel(options.data)
            }
        }
    }
    var model = ko.mapping.fromJS(person, mapping);

    // additional person logic and observables

    return model;
}

PersonEditor.LocationViewModel = function (location) {
    var model = ko.mapping.fromJS(location);

    // additional location logic and observables

    return model;
}

// the following is all you put on the page

<script src="/path/to/person-editor.js"></script>
<script>
    $(document).ready(function () {
        var person = @Html.Raw(@Json.Encode(Model))
        PersonEditor.Init(person);
    });
</script>

然后,您只需将选择列表绑定到位置数组:

<p>
    Your country:
    <select data-bind="options: Locations, optionsText: 'Text', optionsValue: 'Value', value: LocationId, optionsCaption: 'Choose...'">
    </select>
</p>

答案 1 :(得分:0)

根据您更新的问题,执行此操作。

1.我们实际上不需要locationsArray。它已经在user.Locations(愚蠢的我) 2.在Index.cshtml上,页面改变了这样的JavaScript。

var userObject = @Html.Raw(Json.Encode(Model)); // no need for the quotes here
jQuery(document).ready(function ($) {
    Person.PrepareKo();
    Person.EvaluateJqueryUI();
    Person.RegisterUIEventHandlers();
    Person.SaveUrl = "@Url.Action("SavePersonDetails", "Knockout")";
    Person.ViewModel = {
        user : ko.observable(userObject)
    };
    Person.BindUIwithViewModel(Person.ViewModel);
});

3.在Person.js页面上,在RegisterUIEventHandlers #Save按钮点击事件中,执行此操作。

$('#Save').click(function (e) {
    var updatedUser = Person.ViewModel.user();
    updatedUser.Locations.length = 0; // not mandatory, but we don't need to send extra payload back to server.
    // 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: Person.SaveUrl,
            data: ko.toJSON(updatedUser), // Data Transfer Object
            contentType: 'application/json',
            beforeSend: function () {
                // Display loading image
            }
        }).done(function(result) {
            // Handle the response here.
            if (result > 0) {
                alert("Saved");
            } else {
                alert("There was an issue");
            }
        }).fail(function(jqXHR, textStatus, errorThrown) {
            // Handle error.
        }).always(function() {
            // Hide loading image.
        });
    }
});

5.最后,我们的标记

<p data-bind="with: user">
    Your country:
    <select data-bind="options: Locations, 
                optionsText: 'Text', 
                optionsValue: 'Value', 
                value: LocationId, 
                optionsCaption: 'Choose...'">
    </select>
</p>

在不相关的旁注上,

  

jqXHR.success(),jqXHR.error()和jqXHR.complete()回调是   自jQuery 1.8起不推荐使用。准备最终的代码   删除,改为使用jqXHR.done(),jqXHR.fail()和jqXHR.always()。