Reputation: 84
I currently have a list of students and a list of classes in a school object. I would like each class object to be able to display a filtered list of students based on the class id property.
I have tried to access the parent object via custom binding but have not had any success.
Perhaps I am looking at the problem the wrong way? I have spent a couple days on this and whichever way I tackle it I always need to access a value on a parent object.
Are there any methods of accessing what I need? I am beginning to think that it is not possible to access a global style variable.
function School()
{
var self = this;
self.ClassVMs = ko.observableArray([]).indexed('Number');
self.ChildVMs = ko.observableArray([]).indexed('Number');
}
function ClassVM(classId, text)
{
var self = this;
self.Number = ko.observable();
self.Text = ko.observable(text);
self.ClassId = ko.observable(classId);
}
function ChildVM(classId, text)
{
var self = this;
self.Number = ko.observable();
self.ClassId = ko.observable(classId);
self.Text = ko.observable(text);
}
I have a Fiddle with my setup. Any and all guidance is appreciated. Thanks
Upvotes: 2
Views: 285
Reputation: 84
Yes I can see how it could be considered a bit of an XY problem. You both made look at it differently which was a great help.
The way I originally would have liked wasn't looking feasible. My compromise was to add a computed to the root view model and display the list this way.
function School()
{
var self = this;
self.ClassVMs = ko.observableArray([]).indexed('Number');
self.ChildVMs = ko.observableArray([]).indexed('Number');
self.DisplayClassId = ko.observable(1);
self.Display = function(x)
{
console.log(x);
self.DisplayClassId(x);
}
}
var viewModel = new School();
viewModel.filteredItems = ko.computed(function () {
var filter = viewModel.DisplayClassId();
if (!filter) {
return viewModel.ChildVMs();
} else {
var filtered = ko.utils.arrayFilter(viewModel.ChildVMs(), function (item) {
return (item.ClassId() === filter);
});
return filtered;
}
})
Upvotes: 1
Reputation: 63790
You do not need global variables to solve this. Knockout has $root
and $parent
to step slightly outside of the scope you're in inside a foreach
. In addition, if really needed, you can always make sure the view models get another type of view model as its dependency. In fact, if one view model has a list of sub view models it already has such a dependency.
What you need to think about is what your UI/UX is going to be like. Are you designing your view models to support a view where the user is "viewing" a student and enrolls him/her in classes? Or is the app user viewing a class and adding students one by one?
Here's a variant that shows a little bit of both:
function School(classes) {
var self = this;
self.classes = ko.observableArray(classes);
self.students = ko.observableArray([]);
self.enroll = function(child, someClass) {
if (self.students().indexOf(child) < 0) {
self.students.push(child);
}
if (someClass.students().indexOf(child) < 0) {
someClass.students.push(child);
}
};
self.enrollNewChild = function(someClass) {
if (!!someClass.childToBeEnrolled()) {
self.enroll(someClass.childToBeEnrolled(), someClass);
someClass.childToBeEnrolled(null);
}
};
self.enrollInClass = function(child) {
if (!!child.classToBeEnrolledIn()) {
self.enroll(child, child.classToBeEnrolledIn());
child.classToBeEnrolledIn(null);
}
};
}
function Class(id, txt) {
var self = this;
self.id = ko.observable(id);
self.txt = ko.observable(txt);
self.students = ko.observableArray([]);
self.studentsCsv = ko.computed(function() {
return self.students().map(function(s) { return s.txt(); }).join(", ");
});
self.childToBeEnrolled = ko.observable(null);
}
function Child(id, txt) {
var self = this;
self.id = ko.observable(id);
self.txt = ko.observable(txt);
self.classToBeEnrolledIn = ko.observable(null);
}
var english = new Class(1, "English 1");
var math1 = new Class(2, "Mathematics 1");
var math2 = new Class(2, "Mathematics 2");
var john = new Child(1, "John Doe");
var mary = new Child(1, "Mary Roe");
var rick = new Child(1, "Rick Roll");
var marc = new Child(1, "Marcus Aurelius");
var school = new School([english, math1, math2]);
ko.applyBindings(school);
// Method 1:
school.enroll(john, english);
school.enroll(john, math2);
school.enroll(marc, english);
school.enroll(mary, math2);
school.enroll(mary, english);
school.enroll(rick, english);
school.enroll(rick, math1);
td { background-color: #eee; padding: 2px 10px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<h3>Classes:</h3>
<table>
<tbody data-bind="foreach: classes">
<tr>
<td><span data-bind="text: txt"></span></td>
<td><span data-bind="text: studentsCsv"></span></td>
<td>
Add student
<select data-bind="options: $root.students, value: childToBeEnrolled, optionsText: 'txt', optionsCaption: 'Choose...'"></select>
<button data-bind="click: $root.enrollNewChild">enroll now</button>
</td>
</tr>
</tbody>
</table>
<h3>School Students</h3>
<table>
<tbody data-bind="foreach: students">
<tr>
<td><span data-bind="text: txt"></span></td>
<td>
Enroll in:
<select data-bind="options: $root.classes, optionsText: 'txt', value: classToBeEnrolledIn, optionsCaption: 'Choose...'"></select>
<button data-bind="click: $root.enrollInClass">enroll now</button>
</td>
</tr>
</tbody>
</table>
It does not answer your question directly ("display a filtered list of students based on the class id property") because I think that's an XY-problem, and you're better off trying to find a solution like the above where you have proper references instead of having to use id
and some kind of lookup mechanism.
Upvotes: 2