简体   繁体   English

具有Knockout.js和MVC的jQuery日期选择器

[英]jQuery Date Picker with Knockout.js and MVC

I've got a MVC application that I'm getting along OK with. 我有一个与之相处的MVC应用程序。 I'm using knockout.js for binding and using it on an edit page where you can edit child records also. 我正在使用基因敲除.js进行绑定,并在一个编辑页面上使用它,您还可以在其中编辑子记录。 Everything seems to be working OK apart from when I click save, all the Date Pickers reset to todays date. 除了单击“保存”以外,所有的“日期选择器”都重置为今天的日期,一切似乎都可以正常工作。 The data that was saved is what is persisted to the DB but the data changes in all the Date Pickers on that page. 保存的数据将保存到数据库中,但是该页面上所有日期选择器中的数据都会更改。 A refresh of the page shows the correct persisted data. 页面刷新显示正确的持久数据。

Before: 之前: 保存数据之前。

After: 后: 保存数据后。

Here is my code: 这是我的代码:

Edit Page: 编辑页面:

@model SPMVC.Models.SeasonViewModel
@using Newtonsoft.Json

@{
    ViewBag.Title = "Edit Season";
}

@{ string data = JsonConvert.SerializeObject(Model); }

@section scripts
{
    <link rel="stylesheet" href="//code.jquery.com/ui/1.11.1/themes/smoothness/jquery-ui.css">
    <script src="~/Scripts/jquery.validate.js"></script>
    <script src="~/Scripts/jquery-ui-1.11.1.js"></script>
    <script src="~/Scripts/knockout-3.2.0.js"></script>
    <script src="~/Scripts/knockout.mapping-latest.js"></script>
    <script src="~/Scripts/moment.js"></script>

    <link href="~/Content/select2.css" rel="stylesheet" />
    <script src="~/Scripts/select2.js"></script>

    <script>
        $(document).ready(function () { $("#e1").select2(); });
    </script>



    <script src="~/Scripts/seasonsviewmodel.js"></script>
    <script type="text/javascript">
        var seasonViewModel = new SeasonViewModel(@Html.Raw(data));
        ko.applyBindings(seasonViewModel);
    </script>
}

@Html.Partial("_EditSeason")

Edit Partial View: 编辑局部视图:

<h2>@ViewBag.Title</h2>

<p data-bind="text: MessageToClient"></p>

<div class="form-group">
    <label class="control-label" for="SeasonDescription">Season Description</label>
    <input class="form-control" name="SeasonDescription" id="SeasonDescription" data-bind="value: SeasonDescription, event: {change: flagSeasonAsEdited}, hasfocus: true" />
</div>
<div class="form-group">
    <label class="control-label" for="StartDate">Start Date</label>
    <input class="form-control" type="text" name="StartDate" id="StartDate" data-bind="datepicker: StartDate, datepickerOptions: {dateFormat: 'DD, MM d, yy'}, event: {change: flagSeasonAsEdited}" />
</div>
<div class="form-group">
    <label class="control-label" for="Publish">Publish</label>
    <input class="checkbox" name="Publish" id="Publish" type="checkbox" data-bind="checked: Publish, event: {change: flagSeasonAsEdited}" />
</div>

<table class="table table-striped">
    <tr>
        <th>Game Description</th>
        <th>Game Type</th>
        <th>Game Date</th>
        <th>Publish to Schedule</th>
        <th>Publish Results</th>
        <th><button data-bind="click: addGame" class="btn btn-info btn-xs">Add</button></th>
    </tr>
    <tbody data-bind="foreach: Games">
        <tr>
            <td class="form-group"><input class="form-control input-sm" data-bind="value: GameDescription, event: {change: flagGameAsEdited}, hasfocus: true" /></td>
            <td class="form-group"><select data-bind="options: $parent.gameTypes, optionsText: 'GameTypeDescription', optionsValue: 'Id', value: GameTypeId, select2: {  }, event: {change: flagGameAsEdited}"></select></td>
            <td class="form-group"><input class="form-control input-sm" type="text" data-bind="datepicker: GameDate, datepickerOptions: {dateFormat: 'DD, MM d, yy'}, event: {change: flagGameAsEdited}" /></td>
            <td class="form-group"><input class="checkbox" type="checkbox" data-bind="checked: PublishToSchedule, event: {change: flagGameAsEdited}"></td>
            <td class="form-group"><input class="checkbox" type="checkbox" data-bind="checked: PublishResults, event: {change: flagGameAsEdited}"></td>
            <td class="form-group"><button data-bind="click: $parent.deleteGame" class="btn btn-danger btn-xs">Delete</button></td>
        </tr>
    </tbody>
</table>


<p><button data-bind="click: save" class="btn btn-primary">Save</button></p>



<p><a href="/Admin/Seasons/" class="btn btn-default btn-sm">&laquo; Back to List</a></p>

Knockout View Model: 剔除视图模型:

SeasonViewModel = function (data) {
    var self = this;

    ko.mapping.fromJS(data, gameMapping, self);

    self.save = function () {
        $.ajax({
            url: "/Seasons/Save/",
            type: "POST",
            data: ko.toJSON(self),
            contentType: "application/json",
            success: function (data) {
                // TODO: When re mapping the view model after save, all dates are in date2??
                if (data.seasonViewModel != null)
                    ko.mapping.fromJS(data.seasonViewModel, {}, self);

                if (data.newLocation != null)
                    window.location = data.newLocation;
            }
        });
    },

    self.flagSeasonAsEdited = function () {
        if (self.ObjectState() != ObjectState.Added) {
            self.ObjectState(ObjectState.Modified);
        }

        return true;
    },

    self.addGame = function () {
        // Game defaults
        var game = new GameViewModel({ Id: 0, GameDescription: "", GameTypeId: 1, GameDate: new Date(), PublishToSchedule: false, PublishResults: false, ObjectState: ObjectState.Added })
        self.Games.push(game);
    },

    self.deleteGame = function (game) {
        self.Games.remove(this);

        if (game.Id() > 0 && self.GamesToDelete.indexOf(game.Id()) == -1)
            self.GamesToDelete.push(game.Id());
    };
};

GameViewModel = function (data) {
    var self = this;

    ko.mapping.fromJS(data, gameMapping, self);

    self.flagGameAsEdited = function () {
        if (self.ObjectState() != ObjectState.Added) {
            self.ObjectState(ObjectState.Modified);
        }

        return true;
    };
};

GameTypeViewModel = function (data) {
    var self = this;

    ko.mapping.fromJS(data, gameTypeMapping, self);
    ko.applyBindings(new GameTypeViewModel());
};

var gameMapping = {
    'Games': {
        key: function (game) {
            return ko.utils.unwrapObservable(game.Id);
        },
        create: function (options) {
            return new GameViewModel(options.data);
        }
    }
};

var gameTypeMapping = {
    'gameTypes': {
        key: function (gameType) {
            return ko.utils.unwrapObservable(gameType.Id);
        },
        create: function (options) {
            return new GameTypeViewModel(options.data);
        }
    }
};

var ObjectState = {
    Unchanged: 0,
    Added: 1,
    Modified: 2,
    Deleted: 3
};

// Custom Knockout binding handler for date picker
ko.bindingHandlers.datepicker = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        var options = allBindingsAccessor().datepickerOptions || {};
        $(element).datepicker(options);

        ko.utils.registerEventHandler(element, "change", function () {
            var observable = valueAccessor();
            observable($(element).datepicker("getDate"));
        });

        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            $(element).datepicker("destroy");
        });

    },

    update: function (element, valueAccessor) {
        var value = new Date(ko.utils.unwrapObservable(valueAccessor()));
        var current = $(element).datepicker("getDate");

        // Prevents the datepicker from popping up a second time
        if (value - current !== 0) {
            // For some stange reason, Chrome subtracts a day when first displaying the date
            if (navigator.userAgent.indexOf('Chrome') > -1 && value != '') {
                var date = value.getDate() + 1;
                var month = value.getMonth();
                var year = value.getFullYear();
                value = new Date(year, month, date);
            };
            $(element).datepicker("setDate", value);
        }
    }
};

// Custom knockout binding handler for read-only date display
ko.bindingHandlers.dateString = {
    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        var value = valueAccessor();
        var allBindings = allBindingsAccessor();
        var pattern = allBindings.datePattern || 'dd/MM/yyyy';
        var momentObj = moment(ko.utils.unwrapObservable(value), moment.ISO_8601);
        $(element).text(momentObj.format(pattern));
    }
};

ko.bindingHandlers.select2 = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        //    var options = ko.toJS(valueAccessor()) || {};
        //    setTimeout(function () {
        //        $(element).select2(options);
        //    }, 0);
        //}
        var obj = valueAccessor(),
        allBindings = allBindingsAccessor(),
        lookupKey = allBindings.lookupKey;
        $(element).select2(obj);
        if (lookupKey) {
            var value = ko.utils.unwrapObservable(allBindings.value);
            $(element).select2('data', ko.utils.arrayFirst(obj.data.results, function (item) {
                return item[lookupKey] === value;
            }));
        }
    }
};

Can anyone shine any light as to why this behavior would occur? 任何人都可以就为什么会发生这种行为大开眼界吗?

Thanks for taking the time to read this post. 感谢您抽出宝贵的时间阅读这篇文章。

Paul. 保罗。

It is hard to tell without seeing your controller method to see if this is the exact problem but i had a similar issue on a project i was working on. 很难不看您的控制器方法就知道这是否是确切的问题,但是我在一个正在从事的项目中也遇到了类似的问题。

It had to do with how the MVC default JSON formatter works as opposed to the JSON.NET JsonConvert.SerializeObject . 它与MVC默认JSON格式化程序(而不是JSON.NET JsonConvert.SerializeObject

In the success function on your ajax call you access data.seasonViewModel (which i assume is a potentially manipulated copy of the data set passed to the "/Seasons/Save/" call) you are probably getting back MVC's default serialized date format for all of your dates (Which looks something like "/Date(1239018869048)/"). 在ajax调用的success函数中,您可以访问data.seasonViewModel (我认为这是传递给“ / Seasons / Save /”调用的数据集的潜在操纵副本),您可能会取回所有MVC的默认序列化日期格式日期(看起来像“ / Date(1239018869048)/”)。 So your knockout model is being initialized with this value on return instead of the more usable format that is created by JsonConvert.SerializeObject when you first load the page. 因此,剔除模型将在返回时使用此值进行初始化,而不是首次加载页面时由JsonConvert.SerializeObject创建的更有用的格式。

Now when the update function of the custom datepicker binding goes to pull this value out of the observable it uses new Date(ko.utils.unwrapObservable(valueAccessor())) which will try to create a date from the MVC default serialized date format, and fail. 现在,当自定义日期选择器绑定的update功能将该值从可观察值中拉出时,它将使用new Date(ko.utils.unwrapObservable(valueAccessor())) ,它将尝试从MVC默认序列化日期格式创建日期,失败了

If this is the case the solution would be to switch MVC's default JSON formatter to use JSON.NET or create a JsonResult that serializes using JSON.NET for these calls. 如果是这种情况,解决方案是将MVC的默认JSON格式化程序切换为使用JSON.NET或创建一个JsonResult,以使用JSON.NET序列化这些调用。

Couple Examples: 几个例子:

How do I sub in JSON.NET as model binder for ASP.NET MVC controllers? 如何在JSON.NET中将其作为ASP.NET MVC控制器的模型绑定器?

Using JSON.NET as the default JSON serializer in ASP.NET MVC 3 - is it possible? 使用JSON.NET作为ASP.NET MVC 3中的默认JSON序列化程序-可以吗?

Setting the Default JSON Serializer in ASP.NET MVC 在ASP.NET MVC中设置默认JSON序列化器

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM