【问题标题】:KnockoutJS - Setting Select Menu Based on Selection in another MenuKnockoutJS - 根据另一个菜单中的选择设置选择菜单
【发布时间】:2017-05-31 17:57:28
【问题描述】:

我有两个<select> 菜单和一个文本框。 When an option in the first menu is selected, it should update the value in the textbox and it should also set the selected option in the 2nd select menu.

文本框正确更新。

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

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

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

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

这也是JSFiddle 中的代码。

【问题讨论】:

    标签: javascript html knockout.js


    【解决方案1】:

    作为一般规则,请避免在淘汰赛中设置 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>

    【讨论】:

    • 完美运行!谢谢!
    • @Kevin 既然你想构建一个任务编辑器,我认为你会得到更多:jsfiddle.net/dqUAz/1601,而不是上面的答案。仔细比较一下,如果您正确使用淘汰赛,我们在上面所做的(或您在此问题中要求的)都不是真正必要的。
    【解决方案2】:

    这类关系通常可以使用指定readwrite 方法的ko.computed 属性来实现。

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

    write 方法定义新值如何不仅设置底层可观察对象,而且还设置其对任何其他属性的副作用

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

    • read 引用了存储选择的“私有”可观察对象:

      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>

    【讨论】:

      猜你喜欢
      • 2016-01-28
      • 2013-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-17
      相关资源
      最近更新 更多