KnockoutJS - 根据另一个菜单中的选择设置选择菜单

时间:2017-05-31 09:20:57

标签: javascript html5 knockout.js

我有两个<select>菜单和一个文本框。当选择第一个菜单中的选项时,它应该更新文本框中的值,并且还应该在第二个选择菜单中设置所选选项。

文本框正确更新。

但是,第二个选择菜单未更新。

第一个菜单中的ParentID值应用于指定第二个菜单的TaskID值。 ParentID值是同一表中TaskID的FK引用。

例如,在下面,如果在第一个菜单中选择了&#34; ManualItems&#34; ,那么&#34; Positions&#34; 应该成为第二个菜单中选择的值。

&#13;
&#13;
var viewModel = function(data) {
    var self = this;
   
    // variables
    self.currentTask = ko.observable();
    self.selectedParentTask = ko.observable();
    self.taskDescription = ko.observable("");
   
    self.tasks = ko.observableArray([
        {TaskID: 1, TaskName: "ManualItems", TaskDescription: "Manual Rec", ParentID: 4, ParentName: "Positions"},
        {TaskID: 2, TaskName: "Trades", TaskDescription: "Trades Data", ParentID: null, ParentName: null},
        {TaskID: 3, TaskName: "File-In", TaskDescription: "File Detail", ParentID: 2, ParentName: "Trades"},
        {TaskID: 4, TaskName: "Positions", TaskDescription: "Positions Overview", ParentID: null, ParentName: null}
    ]);
    
    self.parentTasks = ko.observableArray([
        {TaskID: 1, TaskName: "ManualItems", TaskDescription: "Manual Rec", ParentID: 4, ParentName: "Positions"},
        {TaskID: 2, TaskName: "Trades", TaskDescription: "Trades Data", ParentID: null, ParentName: null},
        {TaskID: 3, TaskName: "File-In", TaskDescription: "File Detail", ParentID: 2, ParentName: "Trades"},
        {TaskID: 4, TaskName: "Positions", TaskDescription: "Positions Overview", ParentID: null, ParentName: null}
    ]);
    
    /*
    self.currentTask.subscribe(function(newValue){
      self.selectedParentTask(newValue);
    });
    */
    
    self.EditTask = function () {
        // populate all fields with selected task
        self.taskDescription(self.currentTask().TaskDescription);
        self.selectedParentTask(self.currentTask()); // set parent task to the ParentID value of currentTask
    };
};

ko.applyBindings(new viewModel());
&#13;
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<div class="form-group">
    <label for="taskName">Edit Existing Task</label>
    <select class="form-control" id="taskNameSelect" data-bind="
        options: tasks,
        optionsText: 'TaskName',
        value: currentTask,
        event: {change: $root.EditTask},
        optionsCaption: 'Select Task...'
    "></select>
</div>
<div class="form-group">
    <label for="taskParent">Select Parent Task</label>
    <select class="form-control" id="taskParent" data-bind="
        options: parentTasks,
        optionsText: 'TaskName',
        value: selectedParentTask,
        optionsCaption: 'Select Parent Task...'
    "></select>
</div>
<div class="form-group">
    <label for="taskDescription">Task Description</label>
    <textarea class="form-control" id="taskDescription" rows="3" placeholder="Enter Task Description" data-bind="value: taskDescription"></textarea>
</div>
&#13;
&#13;
&#13;

以下是JSFiddle中的代码。

2 个答案:

答案 0 :(得分:0)

通常可以使用指定ko.computedread方法的write属性来实现这种关系。

计算的read属性指向存储“当前选择”的viewmodel的可观察私有属性。

write方法定义新值如何设置基础可观察对象,以及副作用在任何其他属性上。

将此应用于您的第一个下拉列表,该下拉列表写入self.currentTask

  • read引用存储选择的“private”observable:

    read: current,
    
  • write最终存储新选择:

    write: function(task) {
      /* ... */
      current(task)
    }
    
  • 但在写作之前,它会检查是否有匹配的父任务:

    if (task && task.ParentID) {
      // Find parent task with right ID
      var curParent = self.parentTasks()
        .find(function(parent) {
          return parent.TaskID === task.ParentID;
        });
    
      // If it's there, write to parent selection
      if (curParent) {
        parent(curParent);
      }
    }
    

在实施这些计算时,您还会注意到您尚未完全定义所需的关系/用户互动。

剩下的问题:

  • 如果任务有null父母怎么办?
  • 如果用户覆盖父下拉菜单的值会怎样?

在一个工作示例中,没有未定义的行为:

var viewModel = function(data) {
  var self = this;
  
  var current = ko.observable(null);
  var parent = ko.observable(null);
  
  self.tasks = ko.observableArray(tasks());
  self.parentTasks = ko.observableArray(parentTasks());

  self.currentTask = ko.computed({
    read: current,
    write: function(task) {
      if (task && task.ParentID) {
        // Find parent task with right ID
        var curParent = self.parentTasks()
          .find(function(parent) {
            return parent.TaskID === task.ParentID;
          });
          
        // If it's there, write to parent selection
        if (curParent) {
          parent(curParent);
        }
      }
      
      current(task);
    }
  });
  
  self.parentTask = ko.computed({
    read: parent,
    write: function(task) {
      /* To be filled in by the question asker */
      parent(task);
    }
  });
  

  // This can be automated via a `computed`:
  self.taskDescription = ko.pureComputed(function() {
    var current = self.currentTask();
    var parent = self.parentTask();
    
    return (current ? current.TaskDescription : "no existing task") +
      " - (" +
      (parent ? parent.TaskDescription : "no parent task") +
      ")";
  });
};

ko.applyBindings(new viewModel());



function tasks() {
  return [
    {TaskID: 1, TaskName: "ManualItems", TaskDescription: "Manual Rec", ParentID: 4, ParentName: "Positions"},
    {TaskID: 2, TaskName: "Trades", TaskDescription: "Trades Data", ParentID: null, ParentName: null},
    {TaskID: 3, TaskName: "File-In", TaskDescription: "File Detail", ParentID: 2, ParentName: "Trades"},
    {TaskID: 4, TaskName: "Positions", TaskDescription: "Positions Overview", ParentID: null, ParentName: null}
  ];
};

function parentTasks() {
  return [
    {TaskID: 1, TaskName: "ManualItems", TaskDescription: "Manual Rec", ParentID: 4, ParentName: "Positions"},
    {TaskID: 2, TaskName: "Trades", TaskDescription: "Trades Data", ParentID: null, ParentName: null},
    {TaskID: 3, TaskName: "File-In", TaskDescription: "File Detail", ParentID: 2, ParentName: "Trades"},
    {TaskID: 4, TaskName: "Positions", TaskDescription: "Positions Overview", ParentID: null, ParentName: null}
  ];
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="form-group">
    <label for="taskName">Edit Existing Task</label>
    <select class="form-control" id="taskNameSelect"
            data-bind="options: tasks,
                                optionsText: 'TaskName',
                                value: currentTask,
                                optionsCaption: 'Select Task...'"></select>
</div>

<div class="form-group">
    <label for="taskParent">Select Parent Task</label>
    <select class="form-control" id="taskParent"
            data-bind="options: parentTasks,
                                optionsText: 'TaskName',
                                value: parentTask,
                                optionsCaption: 'Select Parent Task...'"></select>
</div>

<div class="form-group">
    <label for="taskDescription">Task Description</label>
    <p class="form-control" data-bind="text: taskDescription"></p>
</div>

答案 1 :(得分:0)

作为一般规则,避免在淘汰赛中设置DOM事件处理程序。大多数情况下,通过使用订阅,您可以使用更少的代码和更少的歧义来做同样的事情。

在这种情况下,您希望通过为其找到匹配的父任务来对currentTask中的更改做出反应。

ko.utils.arrayFirst()是一个方便的utility function,用于从匹配特定条件的数组中提取第一个元素。 (现在您也可以使用Array#find获得相同的效果。)

所以,我们得到:

self.currentTask.subscribe(function (task) {
    var matchingParentTask = ko.utils.arrayFirst(self.parentTasks(), function (parent) {
        return parent.TaskName === task.ParentName;
    });
    self.parentTask(matchingParentTask);
    self.taskDescription(task.TaskDescription);
});

在上下文中:

var viewModel = function(data) {
    var self = this;
   
    // variables
    self.currentTask = ko.observable();
    self.parentTask = ko.observable();
    self.taskDescription = ko.observable();
   
    self.tasks = ko.observableArray([
        {TaskID: 1, TaskName: "ManualItems", TaskDescription: "Manual Rec", ParentID: 4, ParentName: "Positions"},
        {TaskID: 2, TaskName: "Trades", TaskDescription: "Trades Data", ParentID: null, ParentName: null},
        {TaskID: 3, TaskName: "File-In", TaskDescription: "File Detail", ParentID: 2, ParentName: "Trades"},
        {TaskID: 4, TaskName: "Positions", TaskDescription: "Positions Overview", ParentID: null, ParentName: null}
    ]);
    
    self.parentTasks = ko.observableArray([
        {TaskID: 1, TaskName: "ManualItems", TaskDescription: "Manual Rec", ParentID: 4, ParentName: "Positions"},
        {TaskID: 2, TaskName: "Trades", TaskDescription: "Trades Data", ParentID: null, ParentName: null},
        {TaskID: 3, TaskName: "File-In", TaskDescription: "File Detail", ParentID: 2, ParentName: "Trades"},
        {TaskID: 4, TaskName: "Positions", TaskDescription: "Positions Overview", ParentID: null, ParentName: null}
    ]);
    
    self.currentTask.subscribe(function (task) {
        var matchingParentTask = ko.utils.arrayFirst(self.parentTasks(), function (parent) {
            return parent.TaskName === task.ParentName;
        });
        self.parentTask(matchingParentTask);
        self.taskDescription(task.TaskDescription);
    });
};

ko.applyBindings(new viewModel());
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<div class="form-group">
    <label for="taskName">Edit Existing Task</label>
    <select class="form-control" id="taskNameSelect" data-bind="
        value: currentTask,
        options: tasks,
        optionsText: 'TaskName',
        optionsCaption: 'Select Task...'
    "></select>
</div>
<div class="form-group">
    <label for="taskParent">Select Parent Task</label>
    <select class="form-control" id="taskParent" data-bind="
        value: parentTask,
        options: parentTasks,
        optionsText: 'TaskName',
        optionsCaption: 'Select Parent Task...'
    "></select>
</div>
<div class="form-group">
    <label for="taskDescription">Task Description</label>
    <textarea class="form-control" id="taskDescription" rows="3" placeholder="Enter Task Description" data-bind="value: taskDescription"></textarea>
</div>