KnockoutJS - настройка меню выбора на основе выбора в другом меню

1

У меня есть два меню <select> и текстовое поле. Когда выбрана опция в первом меню, она должна обновить значение в текстовом поле, и она также должна установить выбранную опцию во втором меню выбора.

Текстовое поле обновляется правильно.

Однако второе меню выбора не обновляется.

Значение ParentID из первого меню должно использоваться для указания значения TaskID второго меню. Значение ParentID является ссылкой FK на TaskID в той же таблице.

Например, в приведенном ниже примере, если в первом меню выбрано "ManualItems", тогда "Позиции" должны стать значением, выбранным во втором меню.

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.

Теги:
knockout.js

2 ответа

0
Лучший ответ

Как правило, избегайте установки обработчиков событий DOM в нокаут. В большинстве случаев вы можете сделать то же самое с меньшим количеством кода и меньшей двусмысленностью, используя подписки.

В этом случае вы хотите отреагировать на изменения в currentTask, найдя для него соответствующую родительскую задачу.

ko.utils.arrayFirst() - удобная функция полезности, которая вытаскивает первый элемент из массива, который соответствует определенному условию. (В настоящее время вы также можете использовать 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>
  • 0
    Работает отлично! Спасибо!
  • 0
    @Kevin Так как вы хотите создать редактор задач, я думаю, вы получите намного больше с этим: jsfiddle.net/dqUAz/1601, чем с тем, что показывает ответ выше. Сравните, ни одно из того, что мы сделали выше (или то, о чем вы просили в этом вопросе), действительно не нужно, если вы правильно используете нокаут.
0

Такие отношения часто могут быть реализованы с использованием свойств ko.computed которые определяют метод read и write.

Вычисленное свойство read указывает на наблюдаемое личное значение в viewmodel, которое хранит "текущий выбор".

Способы write определяют, как новое значение не только устанавливает базовое наблюдаемое, но также и его побочные эффекты на любые другие свойства.

Применив это к первому выпадающему self.currentTask, который пишет 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 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 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>

Ещё вопросы

Сообщество Overcoder
Наверх
Меню