Example of SchoolCatalog class

Hope you don’t expect me to remember that far back (lol).

My code does not have the parameter. Since it is a method of the School class, all instances of any school will have this method.

  quickFacts() {
    console.log(
      `${this.name} educates ${this.numberOfStudents} students, typically between the ages of ${this.level}.`
    );

Oops! I lied. Was looking at the School class, not the Catalog class.

class SchoolCatalog {
  constructor (level) {
    this._level = level;
    this._list = [];
  }  
  get level () {
    return this._level;
  }
  get list () {
    return this._list;
  }
  addSchool (school) {
    if (school.level === this.level) {
      this.list.push(school);
    } else {
      console.log(`LevelMismatchError: Cannot insert ${school.name} school into the list.`)
    }
  }
  quickFacts (x) {
    console.log(
      `> ${x.name} has ${x.numberOfStudents} ${x.level} school students.`
    );
    if (x.level === 'high') {
      console.log(
        `  Intramural sports programs:\n  ${x.sportsTeams.join(', ')}`
      );
    }
  }
  print () {
    this.list.forEach(this.quickFacts);
  }
}

Notice that we use forEach with this.quickFacts as the callback? x is the iterator variable in the callback function.

Let’s see if I can try to understand this…

Does the print method call the quickFacts method? As quickFacts is a method and not a getter, why are there no parentheses after quickFacts? i.e.
this.list.forEach(this.quickFacts());
Is it because this.quickFacts is acting as forEach's argument here?
As the .forEach() method must take at least one parameter (representing each element in the array iterated over), but as this is “deferred” to quickFacts(), is that why this parameter is represented by x in the quickFacts method instead?
I would have expected the forEach method to be within the quickFacts method itself: something like…

quickFacts() {
  this.list.forEach(school => {
    console.log(`${school.name} educates ${school.numberOfStudents}
${school.level} school students.`);
    if (this.level === 'primary') {
      console.log(school.pickupPolicy);
    }
    if (this.level === 'high') {
      console log(`The school has ${school.sportsTeams.join(', ')} teams`);
    }
  })
}
x.forEach(func())

would invoke the callback and pass the return value. We don’t want that. We want the iterator to invoke the function so we only pass a reference, not a return value. If you are still not up to speed on iterators (or callbacks), then one can understand the mystery it must present as.

Essentially, and perhaps literally, yes. Array.forEach() takes a function (or method) as its first positional argument that it will execute on each iteration of the array object in context.

Array.forEach(function (x) {
    // do something *with*  (but not *to*) `x` such as log
    // the callback should not return anything
    // the method has no return value
})

That would defeat individual instances and always report on a class as a whole. We’re more interested in getting the quick facts on a singular instance, hence pass the reference. In my stripped down version all the method does is report, not enumerate. There no built in logic between allowing the second log out.

It is the print method that orders the logic. Since this.list is an Array object, it has a forEach method… Now the callback is only a reporting mechanism on EACH school in the list. To my mind, this more out front, as it were.

One does suppose I should include logic for other levels, but when I did this it was moot for the cause.

No. It calls the forEach method on this.list. That method in turn takes this.quickFacts as the callback reference which will be invoked repeatedly during iteration of this.list. A particular action will occur on each and every item in the list. This method has no break.

I’ve got it now!
As I’d got so used to seeing .forEach() with the callback function within its parentheses, and as an arrow function without the function keyword, I’d actually forgotten it was a function at all! :crazy_face:
I’d also forgotton that the parameter representing the array elements iterated over is a parameter of this callback function, and NOT of the iterator method.
I now realise that the callback function can be declared before and outside of the iterator method, and that that’s what quickFacts is.
So, quickFacts is passed into the iterator as the function’s identifier.
:smiley::+1:
Thanks for bearing with me, while everything slotted into place…

1 Like

Hi Jon,

It’s been a while since I did this exercise, but this is what I found.

In quickfacts(){}, in my SchoolCatalog class, I have x as an argument because it allows to get the facts of a particular shool within the catalog. Let’s say you have 3 schoolcatalogs eg primary, middle and high.

primary = new SchoolCatalog('primary');
middle = new SchoolCatalog('middle');
high = new SchoolCatalog('high');

Then you add schools in each catalog, eg Salisbury goes in the ‘high’ catalog
high.addSchool(salisbury);

To get quickfacts of a school in a catalog you can use the quickfact(x) method of the SchoolCatalog class
high.quickFacts(salisbury);

To get quickfacts for a school directly, without bothering about wether it is ‘primary’, ‘middle’ or ‘high’, you can use the quickfacts() method in the School class.
salisbury.quickFacts();

It is a bit confusing to have two similar methods, and various ways to get the quick facts of a school.

Hope this helps.

1 Like

Yeh, it confused me at first, but as you can see from my last post, I worked it out in the end. :smiley:
What I had previously failed to realise was that the quickfacts(x){...} method is a callback function defined before and outside the .forEach() iterator method.
When you understand that .quickfacts() is effectively called for each iteration through this.list, it then makes sense that x represents each separate school in the array.

This is a really good example for helping to understand how an iterator method’s callback function can be defined “separately” in this way. :+1:

Hey everyone,
I am trying to develop something similar to your code, but I’ve hit a bump: in quickFacts() I added a restriction for when the method is called using an incorrect school level; everything seemed to be working fine, but it is giving me a different result when I use print()


//Create School class
class School {
  constructor(name, level, numberOfStudents, testScores){
    this._name = name; 
    this._level = level;
    this._numberOfStudents = numberOfStudents;
    this._testScores = testScores;
  }
  get name() {
    return this._name;
  }
  get level() {
    return this._level;
  }
  get numberOfStudents() {
    return this._numberOfStudents;
     }
  set numberOfStudents(num) {
    if (typeof num === 'number'){
    this._numberOfStudents = num;
    } else {
      console.log('Invalid input: numberOfStudents must be set to a Number');
    }    
  }
  get testScores(){
    return this._testScores;
  }
  addTestScore(num){
    if (typeof num === 'number' && num >= 0 && num <= 100){
      this._testScores.push(num);
    } else {
      console.log('Test scores should be a number ranging from 0 to 100')
    }
  }
  getAvgTestScores() {
    let scoreSum = this._testScores.reduce((initial, newScore) => {
      return initial+newScore;
    }, 0);
    let numOfScores = this._testScores.length;
    let avgScore = scoreSum / numOfScores;
    return avgScore.toFixed(2);
  }
  quickFacts(){
    console.log(`${this.name} educates ${this.numberOfStudents} students at the ${this.level} school level.`);
  }
  static pickSubstituteTeacher(substituteTeachers) {
    let randomIndex = Math.floor(Math.random() * substituteTeachers.length);
    return substituteTeachers[randomIndex];
  }
}
// Create PrimarySchool subclass
class PrimarySchool extends School {
  constructor(name, numberOfStudents, pickupPolicy, testScores){
    super(name, 'primary', numberOfStudents, testScores);
    this._pickupPolicy = pickupPolicy;
  }
  get pickupPolicy() {
    return this._pickupPolicy;
  }
}
//Create MiddleSchool subclass
class MiddleSchool extends School {
  constructor(name, numberOfStudents, testScores){
    super(name, 'middle', numberOfStudents, testScores);
}
}
//Create HighSchool subclass
class HighSchool extends School {
  constructor(name, numberOfStudents, sportsTeams, testScores){
    super(name, 'High', numberOfStudents, testScores);
    this._level = 'high';
    this._sportsTeams = sportsTeams;
 }
  get sportsTeams() {
    return this._sportsTeams;
  }
}
//Test and Troubleshoot
const lorraineHansbury = new PrimarySchool('Lorraine Hansbury', 514, 'Students must be picked up by a parent, guardian, or a family member over the age of 13.', [89, 98, 100, 87, 100, 90, 95] );
//console.log(lorraineHansbury);
//lorraineHansbury.quickFacts();
//console.log(School.pickSubstituteTeacher(['Jamal Crawford', 'Lou Williams', 'J. R. Smith', 'James Harden', 'Jason Terry', 'Manu Ginobili']));
const alSmith = new HighSchool('Al E. Smith', 415, ['Baseball', 'Basketball', 'Volleyball', 'Track and Field'], [95, 100, 75, 65, 89, 100, 90, 88, 100, 55, 0]);
//console.log(alSmith.sportsTeams);
//console.log(lorraineHansbury.numberOfStudents);
//console.log(alSmith);
//alSmith.quickFacts();
//console.log(alSmith.getAvgTestScores());
alSmith.addTestScore(100);
//console.log(alSmith.getAvgTestScores());

//Create a SchoolCatalog
class SchoolCatalog {
constructor(level){
  this._list = [];
  this._level = level;
}
get list() {
  return this._list;
}
get level(){
  return this._level;
}
addSchool(school){
  if(this.level === school.level){
  this.list.push(school);
} else {
  console.log(`${school.name} School does not belong in this list! Please add a school in the ${this.level} school level.`)
}
}
quickFacts(school){
  let fact = '';
if (this.level === school.level && school.level === 'primary'){
fact = ` and its pickup policy is: ${school.pickupPolicy}`;
console.log(`${school.name} educates ${school.numberOfStudents} students at the ${school.level} school level ${fact}`);
  } else if (this.level === school.level && school.level === 'middle'){
fact = ` and it is just as boring as any middle school is supposed to be`;
console.log(`${school.name} educates ${school.numberOfStudents} students at the ${school.level} school level ${fact}`);  
  } else if (this.level===school.level && school.level === 'high'){
fact = ` and participates in the following sports: ${school.sportsTeams}`;
console.log(`${school.name} educates ${school.numberOfStudents} students at the ${school.level} school level ${fact}`);
  } else {
    console.log(`Error: Level mismatch`);
  } 
}
print(){
  this.list.forEach(this.quickFacts);
}
}

//Test and Troubleshoot
const primary = new SchoolCatalog('primary');
const middle = new SchoolCatalog('middle');
const high = new SchoolCatalog('high');
high.addSchool(alSmith);
//console.log(middle);
primary.addSchool(lorraineHansbury);
const jfk = new MiddleSchool('J.F.K', 485, '[89, 99, 100, 45, 50, 78, 95]');
const washington = new PrimarySchool('George Washington', 518, "They reall don't care", [89, 95, 75, 80, 100, 100]);
primary.addSchool(washington);
middle.addSchool(jfk);
//console.log(middle);
//primary.quickFacts(lorraineHansbury);
primary.print();

I have been going over this for more than 2 hours and I can’t find the error. Can you take a look? Thanks!

Might be a little more constrained than it needs to be. We are scoped to a list that supposedly contains only schools of that level so not much checking involved. Check this example from an earlier post…

  quickFacts (x) {
    console.log(
      `> ${x.name} has ${x.numberOfStudents} ${x.level} school students.`
    );
    if (x.level === 'high') {
      console.log(
        `  Intramural sports programs:\n  ${x.sportsTeams.join(', ')}`
      );
    }
  }
  print () {
    this.list.forEach(this.quickFacts);
  }
1 Like

Let me try that. I’ll be back

1 Like

I inserted your code there, and it works fine. However, if I call primary.quickFacts(jfk) the code is executed despite jfk having middle as level, which is what I was trying to avoid with the messy code I wrote there. Although - as you point out - I may have taken it a bit too far, I would like to figure out why mine is producing different results when I use print() than those produced by quickfacts()

The code should work for all levels. It does depend upon the lists being strictly composed.

As to ‘why?’ This is not a question we need to analyze unless we’re reverse engineering. If our code isn’t working as we expect then we have a problem. We write code full knowing what it will do. If we don’t know, then time to take that snippet off to the side and play with it.

There are various reasons our well thought out code doesn’t run as expected, not the least would be bad code, but bad math, bad logic, bad reasoning can all be factors.

I use the word ‘bad’ because it is an imaginary word to fill in the space for stuff that doesn’t flesh out as advertised.


Something that is important to zero in on is the separation between the catalog (School) and the other classes. There is no class relation between it and the others.

There will only be as many instances as there are levels. That makes this a utility class so we better do our best to give it some horse power (down the road).

The beauty is that we hand class object instances into this class, not just values. We can chew them up and spit them out any way we see fit, using their own methods for the purpose.

1 Like

You are right, there has to be something about the way I wrote it that I am not catching on. Let me go over that again and I will be back, hopefully with good news. Thanks

1 Like

Fixed it! I had to rewrite print():

print(){
  this.list.forEach(school => {
    this.quickFacts(school)
    
  });
}

I believe that, by calling quickFacts without an argument inside my .forEach statement, it was evaluating this.level === school.level as false.

1 Like