Last active
February 8, 2024 21:59
-
-
Save atilberk/d5eb06bb6b80ac9516ba to your computer and use it in GitHub Desktop.
Simple JS GPA Calculator for Koç University Students.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>KUSIS GPA Calculator</title> | |
<script type="text/javascript" src="http://code.jquery.com/jquery-1.11.2.min.js"></script> | |
<script type="text/javascript"> | |
var cues = { | |
"Course Description Term Grade Units Status Incl GPA":'Chrome', | |
"Course Description Term Grade Units Status Incl GPA":'Firefox', | |
"CourseDescriptionTermGradeUnitsStatusInclGPA":'Nospace' | |
}; | |
var lookup = { | |
'A+':4.3, | |
'A':4., | |
'A-':3.7, | |
'B+':3.3, | |
'B':3., | |
'B-':2.7, | |
'C+':2.3, | |
'C':2., | |
'C-':1.7, | |
'D+':1.3, | |
'D':1., | |
'F':0., | |
'S':0., | |
'U':0., | |
'W':0., | |
'I':0., | |
'AU':0., | |
'T':0., | |
'AP':0., | |
'NA':0., | |
'':0. | |
} | |
var statuses = { | |
"Taken":"", | |
"InProgress":"", | |
"Transferred":"" | |
}; | |
var result = {'gpa':0., 'inConsideration': {}, 'outConsideration': {}}; | |
function isValidInput(candidateInput) { | |
var candidates = candidateInput.split('\n'); | |
var lines = []; | |
for (var i=0;i<candidates.length;i++) { | |
//if (candidates[i].replace(/\t/g,'').length > 0) { | |
if (candidates[i].replace(/\t/g,'').length > 0 && isLetterOrDigitOrSpace(candidates[i][0])) { | |
lines.push(candidates[i]); | |
} | |
} | |
for (var i=0;i<lines.length;i++) { | |
if (lines[i].replace(/ |\t/g,'') in cues) { | |
return (i+7 < lines.length && lines[i+4] in lookup && /^[0-9]+.00$/.test(lines[i+5])); | |
} | |
} | |
return false; | |
} | |
function preprocessInput(rawInput) { | |
var candidates = rawInput.split('\n'); | |
var lines = []; | |
for (var i=0;i<candidates.length;i++) { | |
if (!(candidates[i].replace(/ |\t/g,'') in statuses) && candidates[i].replace(/\t/g,'').length > 0 && isLetterOrDigitOrSpace(candidates[i][0])) { | |
lines.push(candidates[i]); | |
} | |
} | |
var i; | |
for (i=0;i<lines.length;i++) { | |
if (lines[i].replace(/ |\t/g,'') in cues) { | |
i++; | |
break; | |
} | |
} | |
var input = {'inConsideration':{},'outConsideration':{}}; | |
var key = 0 | |
while (i < lines.length && lines[i+3] !== undefined && lines[i+3].trim() in lookup && /^[0-9]+.00$/.test(lines[i+4])) { | |
var semester = lines[i+2].split(' ')[0].substring(0,2).toUpperCase() + "'" + lines[i+2].split(' ')[1].substring(2); | |
if ((lines[i].split(' ')[0] != 'ELC') && // If the course code does not start with ELC | |
( !(lines[i] in courseNameSet(input.inConsideration)) || (lookup[lines[i+3].trim()] > getObjFromCourse(input.inConsideration,lines[i])['grade']) ) // couse is not taken yet, if taken before, check if the grade is better | |
) { | |
if (lines[i] in courseNameSet(input.inConsideration)) { | |
ckey = getKey(input.inConsideration, lines[i]); | |
input.outConsideration[ckey] = Object.create(input.inConsideration[ckey]); | |
delete input.inConsideration[ckey]; | |
} | |
input.inConsideration[key+""] = {'course': lines[i], 'letter':lines[i+3],'grade':lookup[lines[i+3].trim()],'credits':parseFloat(lines[i+4]), 'semester':semester}; | |
key++; | |
} else { | |
input.outConsideration[key+""] = {'course': lines[i], 'letter':lines[i+3],'grade':lookup[lines[i+3].trim()],'credits':parseFloat(lines[i+4]), 'semester':semester}; | |
key++; | |
} | |
i+=6; | |
} | |
return input; | |
} | |
function calculate(input) { | |
var sumGrades = 0.; | |
var sumCredits = 0.; | |
var onCalc = {}; | |
var offCalc = input.outConsideration; | |
for (var key in input.inConsideration) { | |
if (input.inConsideration[key]['letter'] == 'F' || input.inConsideration[key]['grade'] != 0.) { | |
sumGrades += input.inConsideration[key]['grade'] * input.inConsideration[key]['credits']; | |
sumCredits += input.inConsideration[key]['credits']; | |
onCalc[key] = input.inConsideration[key]; | |
} else { | |
offCalc[key] = input.inConsideration[key]; | |
} | |
} | |
return {'gpa':sumGrades / sumCredits, 'inConsideration':onCalc, 'outConsideration': offCalc}; | |
} | |
function courseNameSet(courseObjSet) { | |
var set = {}; | |
for (var key in courseObjSet) { | |
set[(courseObjSet[key].course)] = 0; | |
} | |
return set; | |
} | |
function getKey(set,courseName) { | |
for (var key in set) { | |
if (set[key].course == courseName) { | |
return key; | |
} | |
} | |
return "nokey"; | |
} | |
// returns the course with courseName, the one with the highest grade if multiple | |
function getObjFromCourse(set,courseName) { | |
grade = -1.; | |
element = null; | |
for (var key in set) { | |
e = set[key]; | |
if (e.course == courseName) { | |
g = e.grade; | |
if (grade < g) { | |
grade = g; | |
element = e; | |
} | |
} | |
} | |
return e; | |
} | |
function isLetterOrDigitOrSpace(str) { | |
return str.length === 1 && str.match(/[\sa-z0-9]/i) != null; | |
} | |
$(document).ready(function() { | |
$("#gpa-info").bind("paste", function() { | |
setTimeout(function() { | |
var inputText = $("#gpa-info").val(); | |
if(isValidInput(inputText)) { | |
var input = preprocessInput(inputText); | |
result = calculate(input); | |
$("#result").html( | |
"<span class='success'>Your GPA is " | |
+ "<span class='gpa'>"+result.gpa.toFixed(2)+"</span></span>" | |
); | |
var receipt = "<table id='both'><tr><td><h2>Courses included:</h2></td><td><h2>Courses excluded:</h2></td></tr>" | |
+"<tr><td><table class='receipt-table' id='included'><tr><th></th><th>Course</th><th>Semester</th><th>Letter</th></tr>"; | |
for(var key in result.inConsideration) { | |
receipt += "<tr data-key='" + key + "'><td><span class='addrmbtn rmbtn'>-</span></td><td>"+result.inConsideration[key]['course']+"</td><td>"+result.inConsideration[key]['semester']+"</td><td>"+result.inConsideration[key]['letter']+"</td></tr>"; | |
} | |
receipt += "</table></td>" | |
+"<td><table class='receipt-table' id='excluded'><tr><th></th><th>Course</th><th>Semester</th><th>Letter</th></tr>" | |
for(var key in result.outConsideration) { | |
receipt += "<tr data-key='" + key + "'><td>" | |
+ ((result.outConsideration[key]['grade'] > 0. || result.outConsideration[key]['letter'] == 'F' ) ? "<span class='addrmbtn addbtn'>+</span>" : "") | |
+ "</td><td>"+result.outConsideration[key]['course']+"</td><td>"+result.outConsideration[key]['semester']+"</td><td>"+result.outConsideration[key]['letter']+"</td></tr>"; | |
} | |
receipt += "</table></td></tr></table>" | |
var adder = "<div id='manual-add-div'>" | |
+ "<p><span class='new'>NEW</span>Add a fictional course:</p>" | |
+ "<input id='add-coursecode' type='text' maxlength='8' width='7' placeholder='An alias...'/>" | |
+ "<select id='add-credits'>"; | |
for (var i of [1,3,4]) { | |
adder += "<option"+(i==3 ? " selected": "")+">"+i+"</option>"; | |
} | |
adder += "</select><select id='add-grade'>"; | |
for (var key in lookup) { | |
adder += "<option>"+key+"</option>"; | |
if (key == "F") break; | |
} | |
adder += "</select><button class='add-new-button'>ADD</button></div>"; | |
receipt += adder; | |
$('#receipt').html(receipt); | |
$('#receipt-container').show(); | |
$(document).on("click",".rmbtn",function() { | |
var e = $(this).parents("tr").first(); | |
var key = e.data("key"); | |
result.outConsideration[key] = Object.create(result.inConsideration[key]); | |
delete result.inConsideration[key]; | |
result = calculate(result); | |
e.find(".addrmbtn").removeClass("rmbtn").addClass("addbtn").html("+"); | |
e.remove(); | |
$("#excluded").append(e.prop("outerHTML")); | |
$(".gpa").html(result.gpa.toFixed(2)); | |
}); | |
$(document).on("click",".addbtn", function() { | |
var e = $(this).parents("tr").first(); | |
var key = e.data("key"); | |
result.inConsideration[key] = Object.create(result.outConsideration[key]); | |
delete result.outConsideration[key]; | |
result = calculate(result); | |
e.find(".addrmbtn").removeClass("addbtn").addClass("rmbtn").html("-"); | |
e.remove(); | |
$("#included").append(e.prop("outerHTML")); | |
$(".gpa").html(result.gpa.toFixed(2)); | |
}); | |
$(document).on("click",".add-new-button", function() { | |
//var e = $(this).parents("tr").first(); | |
var key = Object.keys(result.inConsideration).length + Object.keys(result.outConsideration).length; | |
result.inConsideration[key] = Object.create({'course': $('#add-coursecode').val(), 'letter':$('#add-grade option:selected').val(),'grade':lookup[$('#add-grade option:selected').val()],'credits':parseFloat($('#add-credits option:selected').val()), 'semester':'----'}); | |
//delete result.outConsideration[key]; | |
result = calculate(result); | |
//e.find(".addrmbtn").removeClass("addbtn").addClass("rmbtn").html("-"); | |
//e.remove(); | |
newrow = "<tr data-key='" + key + "'><td><span class='addrmbtn rmbtn'>-</span></td><td>"+result.inConsideration[key]['course']+"</td><td>"+result.inConsideration[key]['semester']+"</td><td>"+result.inConsideration[key]['letter']+"</td></tr>"; | |
$("#included").append(newrow); | |
$(".gpa").html(result.gpa.toFixed(2)); | |
}); | |
} else { | |
$("#result").html( | |
"<span class='fail'>Oops. Something is not right." | |
+ "<br> Either you pasted rubbish or it's me." | |
+ "<br> If you think it's me, then please contact my coder.</span>" | |
); | |
} | |
$("#gpa-info").val("PASTED!"); | |
setTimeout(function() { | |
$("#gpa-info").trigger("blur"); | |
}, 1000); | |
}, 0); | |
}); | |
$("#gpa-info").bind("blur", function() { | |
$("#gpa-info").val(""); | |
}); | |
$("#receipt-toggler").click(function() { | |
$("#receipt-toggler").html(($("#receipt").css('display') == 'none' ? "Hide" : "Show") + " receipt"); | |
$("#receipt").animate({'height':'toggle'},800); | |
}); | |
}); | |
</script> | |
<style> | |
body | |
{ | |
font-family: Helvetica; | |
font-size: 14pt; | |
} | |
a | |
{ | |
color: inherit; | |
text-decoration: none; | |
} | |
#calc-wrapper | |
{ | |
width: 44%; | |
margin: auto; | |
color: #2c3e50; | |
} | |
#calc-container | |
{ | |
background-color: #cdd3d7; | |
border-radius: 10px; | |
padding: 10px 20px 20px 20px; | |
} | |
h1 | |
{ | |
text-align: center; | |
} | |
ol | |
{ | |
width: 80%; | |
padding:0; | |
margin: auto; | |
} | |
ol>li | |
{ | |
width: 100%; | |
margin: 10px 0; | |
} | |
.howto | |
{ | |
font-size: 14pt; | |
text-decoration: underline; | |
} | |
.kusis | |
{ | |
background-color: #afd0f1; | |
color: #416291; | |
font-size: 10pt; | |
padding: 0 3px 0 3px; | |
border-radius: 2px; | |
} | |
.key | |
{ | |
background-color: #eee; | |
color: #333; | |
border:1px solid gray; | |
font-size:8pt; | |
box-shadow:1px 0 1px 0 #eee, 0 2px 0 2px #ccc, 0 2px 0 3px #444; | |
-webkit-border-radius:3px; | |
-moz-border-radius:3px; | |
border-radius:3px; | |
margin:2px 3px; | |
padding:1px 5px; | |
} | |
#gpa-info | |
{ | |
width: 100%; | |
min-width: 100%; | |
max-width: 100%; | |
height: 40px; | |
min-height: 40px; | |
max-height: 40px; | |
border: 3px solid #2c3e50; | |
font-size: 20pt; | |
margin: 30px 0 10px 0; | |
} | |
#gpa-info[placeholder] { | |
color: #2c3e50; | |
} | |
#result | |
{ | |
width: 80%; | |
text-align: center; | |
vertical-align: center; | |
font-weight: bold; | |
background-color: #fff; | |
border-radius: 15px; | |
margin: 10px auto; | |
} | |
#result .success | |
{ | |
font-size: 24pt; | |
} | |
#result .gpa | |
{ | |
font-size: 36pt; | |
} | |
#result .fail | |
{ | |
font-size: 14pt; | |
} | |
#receipt-container | |
{ | |
display: none; | |
margin: auto; | |
width: 80%; | |
} | |
#receipt-toggler | |
{ | |
width: 100%; | |
text-align: center; | |
border-radius: 10px; | |
cursor: pointer; | |
font-size: 14pt; | |
margin: auto; | |
} | |
#receipt-toggler:hover | |
{ | |
background-color: #eee; | |
} | |
#receipt | |
{ | |
display: none; | |
color: inherit; | |
} | |
#both | |
{ | |
width: 100%; | |
} | |
#both td, th | |
{ | |
font-size: 8pt; | |
width: 33%; | |
vertical-align: top; | |
text-align: center; | |
} | |
.receipt-table | |
{ | |
text-align: center; | |
width: 100%; | |
border-radius: 10px; | |
background-color: #eee; | |
color: inherit; | |
} | |
#receipt h2 | |
{ | |
margin: 0; | |
} | |
.receipt-table th | |
{ | |
font-size: 12pt; | |
} | |
.receipt-table .addrmbtn | |
{ | |
display: inline-block; | |
cursor: pointer; | |
background-color: rgba(200,200,200,0.7); | |
font-weight: bold; | |
min-width: 10px; | |
border-radius: 10px; | |
} | |
#calc-footer | |
{ | |
opacity: 0.6; | |
font-size: 10pt; | |
text-align: center; | |
margin-top: 10px; | |
font-style: italic; | |
} | |
#calc-footer p | |
{ | |
margin: 5px 0; | |
} | |
#calc-footer a | |
{ | |
color: #2c3e50; | |
text-decoration: none; | |
} | |
#calc-footer a:hover | |
{ | |
text-decoration: underline; | |
} | |
.new | |
{ | |
font-size: 7pt; | |
color: #eee; | |
display: inline-block; | |
padding: 2px; | |
margin-right: 5px; | |
position: relative; | |
bottom: 5px; | |
font-weight: bold; | |
color: white; | |
background-color: rgba(46, 204, 113,1.0); | |
border-radius: 5px; | |
} | |
#manual-add-div | |
{ | |
text-align: center; | |
} | |
#manual-add-div p | |
{ | |
margin-bottom: 5px; | |
} | |
#add-coursecode | |
{ | |
width: 8em; | |
} | |
@media all and (max-width: 768px) { | |
#calc-wrapper | |
{ | |
width: 96%; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div id="calc-wrapper"> | |
<h1>KUSIS GPA Calculator</h1> | |
<div id="calc-container"> | |
<ol> | |
<span class="howto">How to use:</span> | |
<li>Login to <a href="https://kusis.ku.edu.tr" target="blank">KUSIS</a> in <u>English</u> and navigate to <br><span class="kusis">Self Service > Academic Records > My Course History</span></li> | |
<li>Copy the page content by <span class="key">CTRL</span>+<span class="key">A</span> then <span class="key">CTRL</span>+<span class="key">C</span></span></li> | |
<li> | |
Paste it with <span class="key">CTRL</span>+<span class="key">V</span> into the field below | |
<textarea name="gpa-info" id="gpa-info" cols="30" rows="1" placeholder="PASTE HERE!"></textarea> | |
</li> | |
<li> | |
See the receipt and exclude courses! | |
</li> | |
<li> | |
<span class='new'>NEW</span>Add fictional courses to your transcript! | |
</li> | |
</ol> | |
<div id="result"></div> | |
<div id="receipt-container"> | |
<div id="receipt-toggler">Show receipt</div> | |
<div id="receipt"></div> | |
</div> | |
</div> | |
<div id="calc-footer"> | |
<p>Primarily tested on <a href="https://www.mozilla.org/en-US/firefox/new/">Mozilla Firefox</a></p> | |
<p>Source is also available at <a href="https://gist.github.com/atilberk/d5eb06bb6b80ac9516ba">GitHub</a></p> | |
</div> | |
</div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment