Assignment 1: JavaScript fundamentals

Written by Neel Kishnani, Jason Chuen, and Michael

Due Thu Apr 20 11:59pm PT

Submissions not accepted after Sat Apr 22 11:59pm PT.

Backstory: As you embark on your web development journey, you decide to set your sights on something we can all get behind: fixing Axess! And hey, those systems are probably so broken that we may as well start from scratch, right? But before you can take on course enrollments, you'll need a few building blocks first. Besides, as we come up on commencement, maybe you can build up your reputation as a reliable web developer by helping out with that.

This assignment will have you practice some fundamental JavaScript concepts and how to use them in the browser. You'll build a small app to simulate students declaring their major and graduating.

Learning goals

After completing this assignment, you will be able to

Getting started

As with assignment 0, follow these steps to get started.

  1. Download the starter code and extract the .zip file's contents.
  2. Open your command line and navigate to the extracted assign1 directory.
  3. Run npm install.
  4. Run npm start to start the web server.
  5. Open localhost:1930 in your web browser.

Deliverables: We've tried to use boxes like this one to highlight the specific tasks you will need to complete for the assignment. These boxes won't stand alone without the rest of the handout, but consider, for example, using them as a checklist to make sure you've done everything.

Part 1: Warmup exercises

The first part of the assignment consists of a few short warmup exercises, designed to give you practice with JavaScript data structures.

You will write your answers in warmups.js, which has already been included in index.html. We have provided a function testWarmups(), callable from the console, to check your answers.

Task 1a: Using the debugger

The first task will give you practice using the debugger.

  1. To begin, please make sure you replace the TODO in the import line at the top of warmups.js with your SUNetID (login name, not number).
  2. The function debugExercise calls checkAnswer, passing it a "secret code." Your task is to determine the correct code to pass. But since checkAnswer is being imported from debugme.js, located outside of your assign1 folder, you won't be able to modify it (in order to, for example, add a console.log).
  3. Use the debugger to look at the JavaScript code for checkAnswer. Then, call debugExercise from the console and stop the function while it is running so you can inspect the values of the relevant variables to determine what you must pass to it.
  4. Once you have the secret code, replace the TODO in the argument to checkAnswer. Rerunning debugExercise should print a "Success!" message.
  5. Finally, fill in the comment in debugExercise with a brief (1-2 sentence) explanation of your strategy: how did you enter the debugger, and where did you stop? How did you read the secret code?

Some notes for this task:

  • The list of files in the Sources tab is split up by where the files are located. You'll find debugme.js under web.stanford.edu.
  • The secret code is a short string of random characters, different for each student. The script that generates it uses some advanced JavaScript features, so we've tucked it away in another file. You may look at the file if you'd like, but it's not necessary (and probably won't help you get the answer).
  • This being the web and JavaScript, there are ways to "circumvent the process" and solve the task without the debugger. We ask that you avoid these unintended alternate strategies, both because we think the debugger is a valuable tool to have in your toolbox, and because we expect that solving the task using the debugger will be much easier. If, after following the process we've outlined, you want to think about how you might solve it a different way, feel free.

Task 1b: Working with data structures

For the rest of this assignment, you'll work with data for a few sample students and departments.

First, open data.js to have a look at the provided data. The file exports a single JavaScript Object with three entries:

  • students is an Array of Objects, each object representing a student. A student object contains their givenName, their surname, and their sunetid. (Note: These aren't the CAs' actual SUNetIDs.) You can assume that SUNetIDs will be unique across all students in the array.
  • depts is an Array of Objects. Each object contains information about a department: its name (like "Computer Science") and its code (like "CS").
  • units is an Object mapping student SUNetIDs (keys) to the number of units that student has completed (values). You may assume that there will be one entry in the object for each student in students.

This data object has been imported into warmups.js as the variable DATA. Once you have reviewed the data, fill in the functions in warmups.js, using DATA, as follows:

  1. firstNSunets returns the SUNetIDs of the first n students (n is a parameter to the function) in the students array. You can assume there are at least n students.
  2. shortDeptCodes returns an Object whose keys are department names and whose values are their corresponding codes. But only include a department in the result if its code is exactly two characters long.
  3. averageUnits returns the average number of units completed by all students in the data. You can assume there will be at least one student, and you don't need to round or truncate the result.

Each function should be relatively short and will involve some form of iteration. Once you have written these functions, you can use testWarmups (which is exposed to the console) to check that your answers match what we expect.

Note on style and style checking

To help identify some common style issues (and a few functionality issues too), we have includes a configuration file for ESLint.

If you are using VSCode and have installed the ESLint extension, these issues should be highlighted automatically as you code. (If you aren't seeing any highlights, you may have to restart VSCode after you do npm install.)

If you are using something else, or would like to check for lint errors manually, you can run npm run lint in your terminal (you'll have to do this in a different window than the one you ran npm start in).

Note that ESLint won't catch all (or indeed most) style issues. We will give you specific style feedback on your assignment through grading. But having a clean lint run is a good first step!


Part 2: Modeling students and departments

For the remainder of the assignment, you will work with the various other .js files in the public folder. All of these files are imported by index.js. We have included index.js in the provided HTML.

Your next task is to implement two JavaScript classes which we will use to model students and departments in our app.

Task 2a: Student class

Implement the Student class in student.js, and export it as the default (and only) export. An instance of Student will have the following properties (instance variables), which are all "public":

  • sunetid: The student's SUNetID
  • givenName: The student's given name
  • surname: The student's surname
  • dept: The name of the department the student is declared in (a string), or null if the student has not declared yet
  • unitsCompleted: The number of units the student has completed (a number)
  • isAlum: true if the student has graduated, false if not

An instance has the following methods (with corresponding signatures):

  • constructor(sunetid, givenName, surname): Construct a new Student. The student's SUNetID, given name, and surname are as passed in. Students start undeclared, ungraduated, and with zero units completed.
  • fullName(): Return the student's full name, which is their given name, followed by a space, followed by their surname.
  • addUnits(units): Increase the number of units the student has completed by the parameter units.
  • toString(): Return a string representing the student, which should be their given name, a space, their surname, another space, and their SUNetID in parentheses. (For example, "Michael Chang (mchang91)")
  • canGraduate(): Return a boolean indicating whether the student can graduate. A student can graduate if they have declared and have completed at least 180 units. If the student has already graduated, this method should throw an Error with a descriptive message.

Task 2b: Department class

Now write the Department class and export it as the default export in dept.js. An instance of Department has the following "public" properties:

  • name: The full name of the department
  • code: The department code
  • students: An array of the current (non-alum) students who have declared under this department

Implement the following methods:

  • constructor(name, code): Construct a new department with the name and code passed in. Departments start with no current students.
  • toString(): Return the string representation of the department, which is just the department's name.
  • declare(student): Declare the passed-in Student instance under this department, updating the student's and department's instance variables accordingly. If the student is already declared under this department, this method should return without doing anything. But if the student is already declared under a different department, this method should throw an Error with a descriptive error message.
  • graduate(): Check if each current student in the department can graduate; if they can, mark them as an alum and remove them from the list of current students. Return an array of the just-graduated Student instances.

Some notes on these tasks:

  • You may add additional instance variables and methods to these classes if you wish, but you are not required to.
  • It is a good practice to reuse existing functionality where possible. For example, notice how a Student's toString contains their full name.
  • You may assume that a client of these classes will not modify the instance variables in a way that makes the instance inconsistent. For example, a client will not directly set a student's dept without going through declare.
  • You aren't required to do any error checking beyond what is described here.
  • Review the end of lecture 3 for the syntax for throwing Errors. Your errors should generate exceptions that can be caught and handled by the client of your class (which you will do in the next part).

Part 3: The App class

Finally, you'll put this app together by implementing the App class (in app.js).

Task 3a: Console interface

An instance of the App class is constructed by index.js when the page loads. App has the following instance variables:

  • students: An Object mapping students' SUNetIDs (keys) to corresponding Student instances.
  • depts: An Object mapping department codes (keys) to corresponding Department instances.

The constructor has been started for you; you will add to it in the next task. First, implement the following methods:

  • loadData(data): Takes an object with the three keys from the sample data (students, depts, and units; see task 1b) and populates the app's instance variables:
    1. First, reset the students and depts instance variables to be empty. This allows loadData to "reset" the app.
    2. Populate the students map with new Student instances, using the students data.
    3. Update the number of units each student has completed via the units data.
    4. Populate the depts map with new Department instances, based on the passed-in data.
  • declare(sunetid, deptCode): Takes a SUNetID and department code (both strings) and tries to declare that student under that department, then returns the (updated) Student instance. If the SUNetID or department code isn't known to the app, throw an Error with a descriptive message. (If calling declare on the Department causes an error, don't handle it here.)
  • graduate(deptCode): Make the department specified by the passed-in code try to graduate its students, returning the list of graduates (Student instances). If the department code doesn't match a known department, throw an Error with a descriptive message.

You will also need to add imports for Student and Department.

Testing

After completing these methods, you will be able to test your app from end to end:

  • The app instance is exposed to the console. You can inspect its instance variables and call its methods.
  • For example, you could call app.declare(“mchang91", "CS") or app.graduate(“BOGUS"). (Note that loadData is already called for you in index.js.)
  • The function testApp, exposed to the console, will attempt to test a variety of methods from part 2 and this task. If anything doesn't match, it will print out an error message. These tests aren't comprehensive, and we encourage you to add some more tests of your own.

Task 3b: User interaction on the page

Your final task of this assignment is to expose the declare and graduate methods you just wrote to a user of the web page:

  1. Edit the HTML to add a form with two inputs, one for SUNetID and one for department code. These inputs should be labeled so they are identifiable, and make sure to give them ids so you can reference them in JavaScript.
  2. Add two buttons to the form, one for "Declare" and one for "Graduate".
  3. In App, implement the functionality for these buttons:
  4. When the user types in a SUNetID and department code and clicks "Declare", call your app's declare method with the entered values.
  5. If the declaration is successful, display an alert informing the user, including the student's name and the department name. For example, "Michael Chang (mchang91) declared Computer Science!"
  6. If the declaration fails, display an alert with the error message. (Note: It's not enough for the error to be printed to the console, since users won't think to look there. You must catch the error and present it via alert.)
  7. Similarly, implement the "Graduate" button. This button ignores the entered SUNetID. Upon success, display an alert listing the students who graduated. For example,
    Graduates:
    Neel Kishnani (neelk)
    Jason Chuen (jahchuen)
    (You can use \n to add a newline in alert messages.)

Some notes on this task:

  • You do not have to lay out the form controls in any particular way and are not expected to add any styling. As an example, here's how we laid them out: Screenshot of form controls and buttons
  • You will need to add additional methods to App and are free to add instance variables as well. But you should mark them as "private" by prefixing them with an underscore (_).
  • Recall from lecture 4 that you can access the forms on a web page using document.forms, and you can reference the controls of a form using their id or a property of the form.
  • Remember that a <button>l; inside of a <form> defaults to having type="submit", meaning it will refresh the page when clicked. You'll want to change its type to button to avoid this.
  • Don't forget to bind your event handlers! As seen in lecture 4, it is best practice to do this in the constructor (and this is preferred over creating functions on the fly.)

Submitting

When you are finished, please remember to delete your node_modules folder, and then submit your assign2 folder to Paperless.