KnockoutJS绑定问题 - 无法读取属性

时间:2014-01-22 18:41:50

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

我有一个简单的Knockout问题,但我是一个完全的初学者。我被抛弃了这个代码页,其他人已经处理过,但从未完成过。

首次加载此页面时,将检索数据并正确加载主网格。当我尝试自动选择结果中的第一条记录以便在网格下方填写详细列表时,会出现问题。

当发生这种情况时,我收到以下消息。

未捕获的TypeError:无法处理绑定“text:function(){return selected.RequestLog.Timestamp}”,消息:无法读取未定义的属性“Timestamp”

以下是我正在使用的代码片段。返回的数据来自实体框架。

var siteLogModel = function () {
            var self = this;

            self.errorList = ko.observableArray([]);
            self.selected = ko.observable();

            self.updateErrorList = function (page) {
                jQuery.ajax({
                    type: "POST",
                    url: "/Admin/ErrorPage",
                    data: { pageNum: page },
                    success: function (result) {
                        self.errorList(result);
                        self.selected(result[0]);

                        // Since we have success, add the click handler so we can get the details about a row by id.
                        //addRowHandlers();
                    },
                    error: function (result) {
                        jQuery("#status").text = result;
                    }
                });
            };
        };

这是在加载数据后尝试发生的实际绑定。 RequestLog似乎在绑定时不存在,即使确实似乎没问题,如果我在self.selected(result [0])行上面的函数中设置断点。

我认为这是一个范围问题,但我不能为我的生活考虑如何最好地解决它。任何帮助将不胜感激。

    <div class="param">
       <span>Time</span>
       <label data-bind="text: selected.RequestLog.Timestamp"></label>
    </div>

更新:这是文档就绪部分。

jQuery(document).ready(function () {

            var vm = new siteLogModel();
            vm.updateErrorList(0);
            ko.applyBindings(vm);
        });

1 个答案:

答案 0 :(得分:13)

您的selected observable在ko评估绑定表达式时没有.RequestLog属性。该错误来自javascript,而不是ko(虽然ko在您看到的错误消息中包含异常)。运行时,selected.RequestLog === undefined为true,并且您无法在undefined上调用任何内容。它就像一个空引用异常。

如果你在ajax调用结束前调用applyBindings,那就没有意义。

通过执行计算来解决此问题的一种方法是:

<div class="param">
   <span>Time</span>
   <label data-bind="text: selectedRequestLogTimestamp"></label>
</div>

self.selectedRequestLogTimestamp = ko.computed(function() {
    var selected = self.selected();
    return selected && selected.RequestLog
        ? selected.RequestLog.TimeStamp
        : 'Still waiting on data...';
});

通过上述内容,在未定义的变量上不会调用任何内容。在ajax调用完成之前,您的标签将显示“仍在等待数据”,然后只要您调用self.selected(result[0]),它就会填充时间戳。

解决它的另一种方法是保持绑定相同,但通过给选定的observable一个初始值。您可以将所有html保留原样,只需这样:

self.selected = ko.observable({
    RequestLog: {
        TimeStamp: 'Still waiting on data'
    }
});

......你最终会得到相同的结果。

为什么?

每次通过执行self.prop = ko.observable()之类的操作初始化observable时,observable的实际值为undefined。试试吧:

self.prop1 = ko.observable();
var prop1Value = self.prop1();
if (typeof prop1Value === 'undefined') alert('It is undefined');
else alert('this alert will not pop up unless you initialize the observable');

总结一下发生的事情:

  1. 在视图模型中使用等于undefined的值初始化所选的observable。
  2. 您针对viewmodel调用ko.applyBindings。
  3. ko解析数据绑定属性,并尝试绑定。
  4. ko进入text: selected.RequestLog.Timestamp绑定。
  5. ko调用selected(),返回undefined。
  6. ko尝试在undefined上调用.RequestLog。
  7. ko抛出错误,因为undefined没有.RequestLog属性。
  8. 所有这一切都发生在你的ajax调用返回之前。

    回复评论#1

    是的,您可以在ajax成功事件后调用applyBindings。但是,这通常并不总是应该做的事情。如果你愿意,这里有一个如何做的例子:

    self.updateErrorList = function (page) {
        self.updateErrorPromise = jQuery.ajax({
            type: "POST",
            url: "/Admin/ErrorPage",
            data: { pageNum: page },
            success: function (result) {
                self.errorList(result);
                self.selected(result[0]);
            },
            error: function (result) {
                jQuery("#status").text = result;
            }
        });
    };
    
    jQuery(document).ready(function () {
        var vm = new siteLogModel();
        vm.updateErrorList(0);
        vm.updateErrorPromise.done(function() {
            ko.applyBindings(vm);
        });
    });
    

    另一种方法是继续eager-bind(在ajax调用完成之前applyBindings),但是将你的标记包装在if绑定中,如下所示:

    <div class="param" data-bind="if: selected">
       <span>Time</span>
       <label data-bind="text: selected.RequestLog.Timestamp"></label>
    </div>