MrSmith
MrSmith

Reputation: 84

Knockout js displaying a filtered list in a child object. Accessing the parent object list to filter

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

Answers (2)

MrSmith
MrSmith

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

Fiddle for reference

Upvotes: 1

Jeroen
Jeroen

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

Related Questions