【问题标题】:jQuery Date Picker with Knockout.js and MVC带有 Knockout.js 和 MVC 的 jQuery 日期选择器
【发布时间】:2014-12-29 22:55:47
【问题描述】:

我有一个 MVC 应用程序,我相处得很好。我正在使用 knockout.js 进行绑定,并在编辑页面上使用它,您也可以在其中编辑子记录。一切似乎都正常,除了当我单击保存时,所有日期选择器都重置为今天的日期。保存的数据是持久化到数据库的数据,但该页面上所有日期选择器中的数据都会发生变化。刷新页面会显示正确的持久数据。

之前:

之后:

这是我的代码:

编辑页面:

@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")

编辑局部视图:

<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>

淘汰视图模型:

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;
            }));
        }
    }
};

谁能阐明为什么会发生这种行为?

感谢您花时间阅读这篇文章。

保罗。

【问题讨论】:

  • window.location = data.newLocation 的目的尚不清楚,但我猜在重新加载该页面后,日期选择器只是默认为当前日期。删除它或在该点之后检查绑定。
  • 感谢 Mark,我正在学习 Pluralsight 课程(观看并关注现在开始尝试我自己的模型),但我不能 100% 确定为什么我自己有该代码。我评论了它,我仍然得到相同的行为。我假设if (data.seasonViewModel != null) ko.mapping.fromJS(data.seasonViewModel, {}, self); 会相应地重新绑定控件?根据评论,我确实注意到的一件事是日期返回为 date2。也许这可能是问题所在?
  • 哇,你的问题中有很多代码。您是否有机会将其缩减为更小的复制品?
  • 当然,我可以尝试明天再回来做。我发布所有内容的原因是我边走边学,不想错过任何可能导致问题的东西。我很欣赏它增加了建议答案的复杂性,但希望它实际上能帮助某人帮助我解决问题。

标签: javascript jquery asp.net-mvc knockout.js


【解决方案1】:

如果不查看您的控制器方法很难判断这是否是确切的问题,但我在我正在从事的项目中遇到了类似的问题。

这与 MVC 默认 JSON 格式化程序的工作方式有关,而不是 JSON.NET JsonConvert.SerializeObject

在您的 ajax 调用的 success 函数中,您访问 data.seasonViewModel(我假设这是传递给“/Seasons/Save/”调用的数据集的潜在操纵副本),您可能正在恢复 MVC 的默认值所有日期的序列化日期格式(类似于“/Date(1239018869048)/”)。因此,您的淘汰模型在返回时使用此值进行初始化,而不是在您首次加载页面时由JsonConvert.SerializeObject 创建的更有用的格式。

现在,当自定义 datepicker 绑定的 update 函数将该值从 observable 中提取出来时,它使用 new Date(ko.utils.unwrapObservable(valueAccessor())) 尝试从 MVC 默认序列化日期格式创建日期,但失败了。

如果是这种情况,解决方案是将 MVC 的默认 JSON 格式化程序切换为使用 JSON.NET,或者创建一个使用 JSON.NET 对这些调用进行序列化的 JsonResult。

几个例子:

How do I sub in JSON.NET as model binder for ASP.NET MVC controllers?

Using JSON.NET as the default JSON serializer in ASP.NET MVC 3 - is it possible?

Setting the Default JSON Serializer in ASP.NET MVC

【讨论】:

  • 这正是正在发生的事情。您指出我的帖子在一定程度上有所帮助,但 JSON.Net 一旦点击保存控制器操作,似乎就会丢失我的子对象。我实现了 JsonDotNetValueProviderFactory 并添加了 ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType().FirstOrDefault()); ValueProviderFactories.Factories.Add(new JsonDotNetValueProviderFactory());到 Global.asx。我需要通过它的外观进一步研究它。不过到目前为止感谢您的帮助,至少让我看到了问题所在。
  • 我设法找到了解决办法。您对此有何看法?在保存操作控制器方法中,我将最后一行(返回)替换为:var json = Newtonsoft.Json.JsonConvert.SerializeObject(seasonViewModel); return Json(new { json }); 这对我来说似乎工作正常。您认为以后使用此代码有什么问题吗?谢谢。
  • 这会起作用,但为了可重用性,最好将其包装在自定义 JsonResult 中,如下所示:stackoverflow.com/a/7150912/3900634。这样,您就不会将 JsonConvert 调用分散在一堆控制器中。不过,目前这是一个偏好问题。
  • 谢谢。我已将您的答案标记为正确答案。
猜你喜欢
  • 2012-11-17
  • 1970-01-01
  • 2011-09-29
  • 1970-01-01
  • 2016-02-26
  • 1970-01-01
  • 1970-01-01
  • 2010-10-28
  • 1970-01-01
相关资源
最近更新 更多