Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript JavaScript Loops, Arrays and Objects Tracking Data Using Objects The Student Record Search Challenge Solution

Ana Luiza Barreto Marinho
Ana Luiza Barreto Marinho
2,210 Points

Questions

After hours of struggling with this challenge and watching the solution I realized that my first code was only coming up with Dave's information because it wasn't properly iterating through the whole array. Ok... loads of confusion afterwards I watched the video, saw what I was getting wrong (or almost everything), managed to solve the last two problems and add a new little, convenient button with a onclick method. Two thing I still don't understand is, and this may be a silly question:

  1. why can't my error message be inside the second loop?
  2. why is the only possible solution one loop inside the other? I mean... isn't there anything lighter?
var students = [ 
  { 
   name: 'Dave',
   lastName: 'Baker',
    track: 'Front End Development',
    achievements: 158,
    points: 14730
  },
  {
    name: 'Jody',
    lastName: 'Baum',
    track: 'iOS Development with Swift',
    achievements: 175,
    points: 16375
  },
  {
    name: 'Jordan',
    lastName: 'Horn',
    track: 'PHP Development',
    achievements: 55,
    points: 2025
  },
  {
    name: 'Trish',
    lastName: 'King',
    track: 'Learn WordPress',
    achievements: 40,
    points: 1950
  },
  {
    name: 'Trish',
    lastName: 'Brown',
    track: 'Rails Development',
    achievements: 5,
    points: 350
  }
];

// Program Starts
studentSearch();

// variables
var student, input, message, fullName;

// functions
function print(message) {
  var outputDiv = document.getElementById('output');
  outputDiv.innerHTML = message;
}

function getStudentReport(student) {
    var report = '<h2>Student: ' + student.name + ' ' + student.lastName + '</h2>';
    report += '<p>Track: ' + student.track + '</p>';
    report += '<p>Points: ' + student.points + '</p>';
    report += '<p>Achievements: ' + student.achievements + '</p>';
    return report;
}

function btnClick () {
    studentSearch();
}

function studentSearch () {
    while (true) {
        input = prompt('Welcome! Type the name and last name of the student you are looking for.' +
            'When you are done, just type "confirm"');
        if (input === 'confirm' || input === null) {
            break;
        } else if (input !== fullName) {
            message = 'Sorry we can not find this student in our database';
            print(message.fontcolor('red'));
        }
        for (var j = 0; j < students.length; j += 1) {
            student = students[j];
            fullName = student.name + ' ' + student.lastName;
            if (input === fullName) {
                message = getStudentReport(student);
                print(message);
            }
        }
    }
}

4 Answers

Sam Donald
Sam Donald
36,305 Points

Because I don't like while(loops)

  1. You could place the error message inside the for loop. But you would need to make a conditional check to see if you where on the last iteration. (we can do better)
  2. Yes you can do this without the two loops. In fact you can do it with no loops.

My solution

First lets walk through the single loop version. And then we can talk about how to do it without any loop.

// The first thing to note is we were able to remove all the variables
// from the global scope :)

function studentSearch(){
  var search = prompt('Search student records: type a name [Tom] (or "quit" to exit)')
           .toLowerCase().trim();

  if(search != '' && search !== 'quit') {

    var i;
    var student;
    var found = false;// a boolean flag for use later on. 
    var count = students.length;// more on this later.

    for(i=0; i<count; ++i) {
      student = students[i];
      if(student.name.toLowerCase() === search) {
        print(getStudentReport(student));
        found = true;
        break;
      }
    }

    // if our flag is still false then we mustn't have found any student.
    // So we better display that error message.
    if(!found){
      print("Sorry! We could not find any student named "+search);
    }
  }
}studentSearch();

// I removed your outputDiv variable because you just didn't need it.
function print(message) {
  document.getElementById('output').innerHTML = message;
}

// The getStudentReport() function remains unchanged.

You may have noticed I created a variable count and set it equal to students.length. Although this wouldn't make a difference in this use case, it is actually a performance boost to the loop when dealing with larger structures. When you create loops like this:

for(var i=0; i<students.length; ++i) {...}

You are actually recalculating the students.length every iteration through the loop. Since we know the length isn't going to change we can just set it to 'count'.

Now, about that whole no loop thing.

If we can use ECMAScript 2015 we can actually do it with no loop and lose a lot of code along the way.

// Instead of the for loop. And without all those variable declared immediately before it.
var student = students.find(student => student.name.toLowerCase() === search);
if(student) {
    print(getStudentReport(student)) ;
} else {
    print("Sorry there's no student named "+search);
}

And we can actually do even better by losing that if/else conditional and replace it with a ternary operator.

// Instead of the if/else statement above.
student ? // checks the truthiness of student (like an if statement) 
    print(getStudentReport(student)) : // if true we print the report.
    print("Sorry there's no student named "+search);// if false we print the error message.

To learn more about find() and ternary operators

Lastly if any of this feels to confusing to you at the moment don't worry about it. Just continue on your learning path and know that there are many other ways of doing things and the Treehouse teachers will eventually introduce you to everything ( or almost everything )

Jonathon Irizarry
Jonathon Irizarry
9,154 Points

When I run your studentSearch() program with one loop it tells me in the console, "cannot read property 'toLowerCase' of null". Should the methods be placed in another position? Also, is it not best practice to almost always use strict comparison operators such as in the case of your first conditional statement in order to avoid errors and bugs?

Sam Donald
Sam Donald
36,305 Points

Jonathon Irizarry was this happening when you pressed cancel? If so you're right. I was being lazy and just dropped those methods onto the end of the prompt. bad developer :{

We should really be testing to see if search is not null too because, as you've found we can't assign the .toUpperCase() or .trim() methods to a null object. Fortunately it's not a big shift from what we already have.

Example

if(search && search !== "" && search !== "quit") {
    // And then we can assign our methods to search here
    search = search.toLowerCase().trim();

    // Or we can do it within the `for-loops` conditional
    if(student.name.toLowerCase() === search.toLowerCase().trim()){...}
}
Ali Adams
Ali Adams
3,467 Points

Hey, thanks for the solution. I'm curious about the purpose of 'var i' inside of the studentSearch func?

Sam Donald
Sam Donald
36,305 Points

Ali Adams unlike my use of count instead of students.length which offers performance improvements, this is just a convention.

Scope

In JavaScript all variables have function scope (unless they are declared in the global scope, then they have "global scope". So for that reason it's best to declare variables clearly at the beginning of their scope.

// Both scopeTestOne and scopeTestTwo will treat 'i' the same.

function scopeTestOne(){
    var i; // i is accessible anywhere within the function.
    for(i=0; i<5; ++i){
        // Your loop code...
    }
    console.log(i); // i => 4
}

function scopeTestTwo(){
    for(var i=0; i<5; ++i){
        // Your loop code...
    }
    // i is still accessible anywhere within the function.
    console.log(i); // i => 4
}

Note in the above studentSearch() function we could have declared var i through to var count immediately, alongside our search variable declaration. But since there was a chance we would not need them I prefer to delay their declaration until we step into the first if block and know we need them.

Block level scope and let

In ECMAScript 2015 [ES6] we have the let keyword. Unlike var, let has block level scope.

function scopeTestThree(){
    let i; // has function wide scope.
    for(i=0; i<5; ++i){
        // Your loop code...
    }
    console.log(i); // i => 4
}

function scopeTestFour(){
    for(let i=0; i<5; ++i){
        console.log(i); // i => 0...4
    }
    console.log(i); // Uncaught ReferenceError: i is not defined
}

function scopeTestFive(){
    let i; // has function wide scope
    for(let i=0; i<5; ++i){
        // This i takes precedence over the first one.
        // But only lives within this for block.
        console.log(i); // i => 0...4
    }
    console.log(i); // undefined
    // Because it's the function wide scoped `i`, but we never assigned it a value.
}
Ana Luiza Barreto Marinho
Ana Luiza Barreto Marinho
2,210 Points

Jonathon, if you could, please, post the code you wrote with it, it would help a lot... Maybe you made some mistake... And yes, it is best practice to almost always use strict comparison operators...

Jonathon Irizarry
Jonathon Irizarry
9,154 Points

The code is the exact same one he posted ("single loop") and was not modified. I'm just looking for some clarification on why when I run it in the browser I get an error in my Javascript console that says: Uncaught TypeError: Cannot read property 'toLowerCase' of null at studentSearch (students_report.js:3) at students_report.js:24

Ana Luiza Barreto Marinho
Ana Luiza Barreto Marinho
2,210 Points

Works fine here... he did leave out this function

function getStudentReport( student ) {
  var report = '<h2>Student: ' + student.name + '</h2>';
  report += '<p>Track: ' + student.track + '</p>';
  report += '<p>Points: ' + student.points + '</p>';
  report += '<p>Achievements: ' + student.achievements + '</p>';
  return report;
}

If you just put it after the print function it will work just as well as if you put it in the beginning of your code.

Jonathon Irizarry
Jonathon Irizarry
9,154 Points

I was tired when I posted the question and left out the part of the program that was not functioning correctly (hitting cancel). Sam has answered my question and cleared my brain, thank Lord. Thanks for your attempt at helping!