У меня есть два меню <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.
Как правило, избегайте установки обработчиков событий 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>
Такие отношения часто могут быть реализованы с использованием свойств 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>