【问题标题】:Knockout does not update UI when observableArray is a property of other model当 observableArray 是其他模型的属性时,Knockout 不会更新 UI
【发布时间】:2016-06-27 22:05:05
【问题描述】:

我有 2 个 observableArray 相互连接。单击“功能”时,我尝试显示它的“任务”。但是,当我单击功能时,KO 不会更新 UI。在控制台上,我可以跟踪我的 viewModel,并且可以看到在 selectedFeature 上成功加载了任务。但是,UI 不会更新,甚至所有数组都被定义为可观察的。

这是直播demo on fiddle.

请告诉我我在哪里失踪?

function GetFeatures() {
    var url = "/Project/GetFeatures";

    $.get(url, "", function (data) {
        $.each(JSON.parse(data), function (i, item) {
            projectVM.features.push(new featureViewModelCreator(item, projectVM.selectedFeature));
        });
    });

};

function GetTasks(selectedFeature) {
    var url = "/Task/GetTaskList";
    $.get(url, { "FeatureId": selectedFeature.FeatureId }, function (data) {

        $.each(JSON.parse(data), function (i, item) {
            selectedFeature.tasks.push(new taskViewModelCreator(item, selectedFeature.selectedTask));
        });
    });
};

function taskViewModelCreator(data, selected) {
    var self = this;
    self.TaskId = data.TaskId;
    self.Title = data.Name;
    self.Status = data.Status.Name;
    self.CreatedDate = data.CreatedDate;
    self.UserCreatedFullName = data.UserCreated.FullName;

    this.IsSelected = ko.computed(function () {
        return selected() === self;
    });
}

function featureViewModelCreator(data, selected) {
    var self = this;
    self.FeatureId = data.FeatureId;
    self.Name = data.Name;
    self.Status = data.Status.Name;
    self.CreatedDate = data.CreatedDate;
    self.UserCreatedFullName = data.UserCreated.FullName;

    self.tasks = ko.observableArray();

    this.IsSelected = ko.computed(function () {
        return selected() === self;
    });

    self.selectedTask = ko.observable();
    self.taskClicked = function (clickedTask) {
        var selection = ko.utils.arrayFilter(self.model.tasks(), function (item) {
            return clickedTask === item;
        })[0];
        self.selectedTask(selection);
    }
}

function projectViewModelCreator() {
    var self = this;
    self.ProjectId = 1;
    self.features = ko.observableArray();
    self.selectedFeature = ko.observable();

    self.featureClicked = function (clickedFeature) {
        self.selectedFeature(clickedFeature);
        GetTasks(clickedFeature);
    }
}
var projectVM = new projectViewModelCreator();


ko.applyBindings(projectVM, $('.taskmanTable')[0]);

GetFeatures();

在用户界面上

<div class="taskmanTable">
    <table class="table table-hover featureList">
        <thead>
            <tr>
                <th>Title</th>
            </tr>
        </thead>
        <tbody data-bind="foreach: features">
            <tr data-bind="click: $root.featureClicked, css: { active : IsSelected } ">
                <td><span data-bind="text: Name"> </span></td>
            </tr>
        </tbody>
    </table>
    <table class="table table-hover taskList">
        <thead>
            <tr>
                <th>Title</th>
            </tr>
        </thead>
        <tbody data-bind="foreach: selectedFeature.tasks">
            <tr>
                <td><span data-bind="text:Title"></span></td>
            </tr>
        </tbody>
    </table>
</div>

【问题讨论】:

    标签: javascript jquery knockout.js


    【解决方案1】:

    这是带有关键注释的正确版本:here。 KO 文档非常详细。


    您提到了一个关于 UI 代码样式的有趣注释:“据我所知,我们不在 UI 上使用 ()”。我以前没有注意这个事实。

    1. 我们真的可以为 observable 省略括号:ko observable;

    View 包含一个 没有括号的 observable:

    <label>
    <input type="checkbox" data-bind="checked: displayMessage" /> Display message
    </label>
    

    源代码:

    ko.applyBindings({
        displayMessage: ko.observable(false)
    });
    
    1. 我们可以省略 UI 上可观察数组的括号:ko observable array

    视图包含:&lt;ul data-bind="foreach: people"&gt;,而 视图模型有:

    self.people = ko.observableArray([
            { name: 'Bert' },
            { name: 'Charles' },
            { name: 'Denise' }
        ]);
    
    1. 我们可以在 UI 上为“叶”可观察对象或可观察对象数组省略括号。这是您修改后的code sampledata-bind="if: selectedFeature"data-bind="foreach: selectedFeature().tasks"&gt; 仅省略了可观察到的叶大括号。

    2. 最后,我们可以省略 'parent' observables 的括号吗?我们可以通过添加another ko UI-statement 来实现(用而不是if,示例2)。

    with 绑定将动态添加或删除后代元素 取决于关联的值是否为空/未定义

    1. 但是,我相信,我们不能在 UI 语句之外省略父节点的括号,因为它等于一个 javascript 语句:projectVM.selectedfeature().tasks。否则projectVM.selectedfeature.tasks 将不起作用,因为 observables 没有这样的属性任务。相反,一个 observable 包含一个具有该属性的对象,通过方括号 () 调用它来检索该对象。实际上,knockoutjs introduction 页面上有一个示例。 &lt;button data-bind="enable: myItems().length &lt; 5"&gt;Add&lt;/button&gt;

    下面的代码使用了以下事实(可以找到here, example 2):

    了解 if 绑定对于 使此代码正常工作。没有它,当 试图在“Mercury”的上下文中评估 capital.cityName,其中 资本为空。在 JavaScript 中,你不能评估 null 或未定义值的子属性。

    function GetFeatures() {
      var data = {
        Name: "Test Feature",
        FeatureId: 1
      }
      projectVM.features.push(new featureViewModelCreator(data, projectVM.selectedFeature));
    
    };
    
    function GetTasks(selectedFeature) {
      var data = {
          Title: "Test Feature",
          TaskId: 1
      }
      selectedFeature().tasks.push(new taskViewModelCreator(data, selectedFeature().selectedTask));
    };
    
    function taskViewModelCreator(data, selected) {
      var self = this;
      self.TaskId = data.TaskId;
      self.Title = data.Title;
      // Step 3: you can omit $root declaration, I have removed it
      // just to show that the example will work without $root as well.
      // But you can define the root prefix explicitly (declaring explicit
      // scope may help you when you models become more complicated).
      
      // Step 4: data-bind="if: selectedFeature() statement was added
      // to hide the table when it is not defined, this statement also
      // helps us to avoid 'undefined' error.
      
      // Step 5: if the object is defined, we should referense 
      // the observable array via -> () as well. This is the KnockoutJS
      // style we have to make several bugs of that kind in order
      // to use such syntax automatically.
    
      this.IsSelected = ko.computed(function() {
        return selected() === self;
      });
    }
    
    function featureViewModelCreator(data, selected) {
      var self = this;
      self.FeatureId = data.FeatureId;
      self.Name = data.Name;
    
    
      self.tasks = ko.observableArray();
    
      this.IsSelected = ko.computed(function() {
        return selected() === self;
      });
    
      self.selectedTask = ko.observable();
      
    }
    
    function projectViewModelCreator() {
      var self = this;
      self.ProjectId = 1;
      self.features = ko.observableArray();
      self.selectedFeature = ko.observable();
    
      self.featureClicked = function(clickedFeature) {
        self.selectedFeature(clickedFeature);
        GetTasks(self.selectedFeature);
      }
    }
    
    var projectVM = new projectViewModelCreator();
    
    ko.applyBindings(projectVM, $('.taskmanTable')[0]);
    
    GetFeatures();
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <div class="taskmanTable">
      <table class="table table-hover featureList">
        <thead>
          <tr>
            <th>Title</th>
          </tr>
        </thead>
        <tbody data-bind="foreach: features">
          <tr data-bind="click: $root.featureClicked, css: { active : IsSelected } ">
            <td><span data-bind="text: Name"> </span></td>
          </tr>
        </tbody>
      </table>
      <hr/>
      <table data-bind="if: selectedFeature()" class="table table-hover taskList">
        <thead>
          <tr>
            <th>Title</th>
          </tr>
        </thead>
        <tbody data-bind="foreach: selectedFeature().tasks()"><!-- $root -->
          <tr>
            <td><span data-bind="text: Title"></span></td>
          </tr>
        </tbody>
      </table>
    </div>

    【讨论】:

    • 嗨@Spirit,感谢您的回复。我创建了一个 fiddle 我将 $root 添加到我的视图中,这是有道理的。在第二部分,我不确定 clickedFeature。我正在绑定使用 featureViewModel 加载的功能。在 projectVM 上,“特征”被定义为 observableArray。单击视图时,“clickedFeature”只是加载模型的“特征”项。因此,我认为它是一种模态。我对吗?您还需要哪些信息?
    • @JustAnotherCodeLover,您是否尝试在 taskViewModelCreator 中将 .Name 属性更改为 .Title?这在我看来是根本原因。我们应该在视图中使用 () 语法来引用 observable 数组。
    • 感谢您的努力@Spirit。我试过你的小提琴并检查我的代码。 Step1 是我的复制/粘贴错误,OK。 Step2 我的代码没有问题,因为我从服务器返回了 Title。(对于虚拟数据,你是对的)。正如您所说,Step3 是可选的。第 4 步绝对是问题的根源。 Step5 对 UI 没有影响。使用(),只是将JS数组转换为KO数组。但是在 VM 上,我更改了 featureClicked 函数行“GetTasks(clickedFeature)”并且没有在 GetTasks 函数中使用“()”。它又可以正常工作了。
    • 在 UI 上,我将数据绑定从“foreach: selectedFeature().tasks()”更改为“foreach: selectedFeature.tasks”。它说,代码很好,功能加载,但任务不工作(没有错误)。然后我删除了“if: selectedFeature()”数据绑定标签。使用“foreach:selectedFeature().tasks()”会引发错误。当我将其更改为“foreach:selectedFeature.tasks”时,它不会在控制台上抛出错误,但无法加载任务。 “()”的主要问题是什么?据我所知,我们不在 UI 上使用 ()。我想不通。
    • @JustAnotherCodeLover,我们可以在 UI 上为“叶”可观察对象或可观察对象数组省略括号,但对于链式可观察对象,我们不能这样做。 +1 你向我打开了一个有趣的事实。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-18
    • 1970-01-01
    • 1970-01-01
    • 2013-07-25
    • 1970-01-01
    • 2021-11-16
    • 1970-01-01
    相关资源
    最近更新 更多