One question that I am asked over and over again by students is “what will happen to my GPA if I take courses X and Y, and get a certain score in them?” I have been asked this question often enough that I decided to automate a reply. I wrote a simple JavaScript page on my work website to allow students to test different scenarios and see how they affect their GPA.

First, I had to decide how I wanted to do this. I went for the simplest possible user interface for now, as I wanted to get this to students as soon as possible. So the current interface asks students to enter their current GPA and hours achieved so far. It then asks them to choose how many new courses they wish to take, as well as how many old courses they would like to retake to improve their GPA.

Once the students do this, they are requested to enter information for the courses they have chosen to enter. This information includes the credit hours of the course, the grade the student expects to get, and, if this is a course that is going to be retaken, the previous grade obtained in the course. The expected GPA of the student is then calculated using this information.

Below is the html file used to display this interface to the user:

<!DOCTYPE html>
<html>
 <head>
        <meta charset="utf-8">
        <meta name="description" content="Simple GPA Calculator.">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>GPA Calculator</title>
        <link rel="stylesheet" type="text/css" href="style.css">
        </head>

<body>

<div class="container">
    <header>
      <img src="logo.png" alt="Logo" height="150" width="220">
      <p class="tagline">Innovate. Engineer. Educate.</p>
    </header>
    <nav>
      <ul>
        <li><a href="index.html">About</a></li>
        <li><a href="books.html">Books</a></li>
        <li><a href="publications.html">Publications</a></li>
        <li><a href="links.html">Links</a></li>
        <li><a href="mygrades.html">Get My Grades</a></li>
        <li><a href="games.html">Games</a></li> 
        <li><a href="dogpa.html">GPA Calculator</a></li>
      </ul>
    </nav>
    <section class="owner-section">
<h2>GPA Calculator</h2>

<div id="inputSection">
  <div id="zeroth">
        <p> Please enter your current GPA and the number of hours you have achieved </p>
        <label for="currentGPA">Current GPA:</label>
        <input type="number" id="currentGPA" name="currentGPA" min="0" max="4.00"><br>
        <label for="currentHours">Total Achieved Hours:</label>
        <input type="number" id="currentHours" name="currentHours" min="0">
   </div>
  <br> 
  <div id="first">
        <p> Please enter the number of new courses you wish to take, then press the Read New Courses Info button. Once the fields for your new courses appear, please enter the credit hours of each course and the grade you expect to get. You can modify all values at any time. But if you change the number of courses, please click on the button to regenerate the fields for the number of courses you want.Please enter all grades in capital (A,A+,B,etc)</p>
        <label for="inpnewCourses">Enter the number of new courses:</label>
        <input type="number" id="inpnewCourses" name="inpnewCourses" />

        <div id="divnewCoursesFields">
                <!-- New course fields will be added here -->
        </div>  


        <button onclick="readNew()">Read New Courses Info</button>
  </div>

  <br>

  <div id="second">
        <p> Repeat the same as above, but for old courses you want to re-register to improve. You need to enter your old grade in addition to the above information. </p>
        <label for="inpoldCourses">Enter the number of old courses:</label>
        <input type="number" id="inpoldCourses" name="inpoldCourses" />

        <div id="divoldCoursesFields">
                <!-- New course fields will be added here -->
        </div>


        <button onclick="readOld()">Read Old Courses Info</button>
  </div>

</div>

<p> Press on this button when you are ready to calculate your GPA</p>
<button onclick="calculateGPA()">Calculate GPA</button>

<p id="newGPA"></p>

<script src="gpaCalculator.js"></script> 

        </section>
  </div>
  <footer>
    © Sherif Fadel Fahmy 2023. All rights reserved.
  </footer>

</body>
</html>

As you can see, the code calls three functions from the JavaScript file gpaCalculator.js. Namely, readNew(), readOld() and calculateGPA(). The three functions are tied to the onclick even of buttons on the user interface. The content of the JavaScript file is displayed below, I will explain what each function does afterwards.

let current_GPA, current_hours, new_courses, old_courses;
let new_hours_sum = 0, old_hours_sum = 0, new_sum_GPA = 0, old_sum_GPA = 0, old_before_GPA = 0;
const gradePoints = {
    'A+': 12/3,
    'A': 11.5/3,
    'A-': 11/3,
    'B+': 10/3,
    'B': 9/3,
    'B-': 8/3,
    'C+': 7/3,
    'C': 6/3,
    'C-': 5/3,
    'D+': 4/3,
    'D': 3/3,
    'F': 0
};

function addNewCourseField(index) {
    let newCoursesFieldsDiv = document.getElementById('divnewCoursesFields');
    let div = document.createElement('div');
    div.innerHTML = `<label for="newCourseHours${index}">Credit Hours for New Course ${index}:</label>
                     <input type="number" id="newCourseHours${index}" name="newCourse${index}Hours" min="0"><br>
                     <label for="newCourseGrade${index}">Expected Grade for New Course ${index}:</label>
                     <input type="text" id="newCourseGrade${index}" name="newCourse${index}Grade"><br>`;
    newCoursesFieldsDiv.appendChild(div);
}

function addOldCourseField(index) {
    let oldCoursesFieldsDiv = document.getElementById('divoldCoursesFields');
    let div = document.createElement('div');
    div.innerHTML = `<label for="oldCourseHours${index}">Credit Hours for Old Course ${index}:</label>
                     <input type="number" id="oldCourseHours${index}" name="oldCourse${index}Hours" min="0"><br>
                     <label for="oldCourseCurrentGrade${index}">Current Grade for Old Course ${index}:</label>
                     <input type="text" id="oldCourseCurrentGrade${index}" name="oldCourse${index}CurrentGrade"><br>
                     <label for="oldCourseExpectedGrade${index}">Expected Grade for Old Course ${index}:</label>
                     <input type="text" id="oldCourseExpectedGrade${index}" name="oldCourse${index}ExpectedGrade"><br>`;
    oldCoursesFieldsDiv.appendChild(div);
}

function readNew(){
    let theData = document.getElementById('inpnewCourses');
    let newCoursesFieldsDiv = document.getElementById('divnewCoursesFields');
    newCoursesFieldsDiv.innerHTML = '';
    newCourses = Number(theData.value);
    for (let i = 1; i <= newCourses; i++) {
        addNewCourseField(i);
    }
}

function readOld(){

    let theData = document.getElementById('inpoldCourses');
    let oldCoursesFieldsDiv = document.getElementById('divoldCoursesFields');
    oldCoursesFieldsDiv.innerHTML = '';
    oldCourses = Number(theData.value);
    for (let i = 1; i <= oldCourses; i++) {
       addOldCourseField(i);
    }
}


function calculateGPA() {
    current_GPA = Number(document.getElementById('currentGPA').value);
    current_hours = Number(document.getElementById('currentHours').value);

    let new_courses_fields = document.querySelectorAll('[id^="newCourse"]');
    for (let field of new_courses_fields) {
            if (field.id.includes("newCourseHour")){
                let hours = Number(field.value);
                let grade = document.getElementById(field.id.replace('Hours', 'Grade')).value;
                new_hours_sum += hours;
                new_sum_GPA += gradePoints[grade] * hours;
            }
    }

    let old_courses_fields = document.querySelectorAll('[id^="oldCourse"]');
    for (let field of old_courses_fields) {
            if (field.id.includes("oldCourseHour")){
                let hours = Number(field.value);
                let currentGrade = document.getElementById(field.id.replace('Hours', 'CurrentGrade')).value;
                let expectedGrade = document.getElementById(field.id.replace('Hours', 'ExpectedGrade')).value;
                old_hours_sum += hours;
                old_sum_GPA += gradePoints[expectedGrade] * hours;
                if (currentGrade!="W") { 
                        old_before_GPA += gradePoints[currentGrade] * hours;
                } 

            }
    }

    let newGPA = (current_hours * current_GPA + new_sum_GPA + old_sum_GPA - old_before_GPA) / (current_hours + new_hours_sum);
    document.getElementById("newGPA").innerHTML = "Your new GPA would be: " + newGPA.toFixed(2);

    // Reset sums for next calculation
    new_hours_sum = 0;
    old_hours_sum = 0;
    new_sum_GPA = 0;
    old_sum_GPA = 0;
    old_before_GPA = 0;
}

I tried to keep the code as modular as possible. After declaring the variables to be used in the code, including the mapping between letter grades and GPA points, I define a set of functions. The first two, addOldCourseField and addNewCourseField take an integer parameter and generate one entry in the user interface representing that course.

For addNewCourseField, the new entries are two — one for the credit hours of the course, and the second for the expected grades of the course. For addOldCourseField, a third textbox is added to the interface — that of the current grade obtained in the course. Note the choice to always have the textbox for the credit hours of the courses first, this design choice was made to make programming easier later on in the code as well as present an consistent interface to the user. The two functions mentioned here only add entries for one course, they need to be called several times in order to create the user interface for multiple courses.

This is where readOld() and readNew() come in. They are triggered by a press of a button and first read the desired number of old and new courses respectively. The do this by retrieving the content of the corresponding textbox by using the getElementById method. Once they get these numbers, they get a reference to the <div> element in the DOM that we previously created in the html to display the information the student needs to enter for each course — the divnewCoursesFields and the divoldCoursesFields. Once this is done, we iteratively add the fields for the courses by calling the addOldCourseField and addNewCourseField functions with the counter of the loop used as the input parameter.

The last function in the code is calculateGPA, this function first retrieves the current GPA and number of achieved hours from the DOM using the getElementByID method. It then gets a list of all elements representing the old courses, and another list representing the new courses. This is done by using the querySelectorAll method. The input to the method [id^=”newCourse”] and [id^=”oldCourse”] select all elements in the DOM that start with “newCourse” and “oldCourse” respectively. The code then iterates over these lists using two separate for loops.

Remember that we chose to place the textbox that represents the credit hours of the course first. Here is were we make use of this design choice. Each of the for loops contain an if statement that checks if the current element has an id indicating that it is the Hours field. If so, this is the start of one course. The value of the credit hours is extracted, and the values of the expected grade and, if this is an old course, the current grade, are obtained by replacing Hours with the corresponding string, either CurrentGrade or ExpectedGrade, using the replace method in the string class.

Now that all the data for one course is available, we calculate its contribution to the GPA by multiplying its credit hours by its expected grade weight, and add its credit hours to a variable representing the number of credits the student is expected to add to their record. For old courses, we also multiply the current grade by the number of credit hours in the course — we need this since we need to subtract this from the student’s GPA before adding the effect of improvement. If this is not done, the effect of the course will appear twice, once when it was first taken, and the second time when it was re-taken to improve the grade — and if statement excludes W grades from this, as a withdrawn course has no effect on current GPA.

Once all this is done, we calculate the GPA using the following formula

let newGPA = (current_hours * current_GPA + new_sum_GPA + old_sum_GPA - old_before_GPA) / (current_hours + new_hours_sum);

Finally, we display the calculated grade to the screen, and reset all variables in preparation for the next calculation. That’s it ladies and gents, this is the simple script that allows us to calculate how taking, or retaking, certain courses will affect GPA. Whenever a student asks me to this this calculation for them, I am going to use this page to do it quickly, will also give it to them to try different combinations at home — so that they can plan how to improve their GPA on their own. Here is the link to the page if you would like to try it out yourself.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.