I was asked by a friend to write a simple page for selecting members to various tasks. The members are listed in a textfile, along with how much they are willing to contribute (Usually a number between 0 and 1). Bellow is an example of such a list:
name, mebershipdegree
Noen, 4
John Doe, 1
The rock, 0.5
Ally MacBeal, 0.121212021
Now my goal was to create a generator that picked one of the names from the list above relative to how much they wanted to contribute. So the leader should show up much more than the other names. My code is working as intended, the ratios seems to work properly. There are however a few things that bugs me
- Is the JavaScript modern, succint and understandable?
- I added a failsaife if the memberlist is not found, is the way to handle this error ok?
- How could the JavaScript file be improved?
- In firefox the site flashes when one reloads the page. This might be because the JavaScript fires before the CSS is loaded.
- Is there a better method to do the fetch part? Now I have to do it twice to be able to reload the page using spacebar (not yet implemented in the live version).
Live version Hvem (Who). GitHub
HTML: index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hvem</title>
<link rel="apple-touch-icon" sizes="180x180" href="./apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="./favicon-16x16.png">
<link rel="manifest" href="./site.webmanifest">
<link rel="mask-icon" href="./safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="index.css">
</head>
<body>
<div class="center">
<h1 id="Noen"></h1>
</div>
<script type="text/javascript" src="hvem.js"></script>
</body>
</html>
CSS: main.css
html,
body {
margin: 0;
height: 100%;
overflow: hidden;
overflow-y: hidden;
}
.center {
height: 100%;
position: relative;
}
.center h1 {
text-align: center;
font-size: 12vw;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
Javascript: hvem.js
document.addEventListener('keyup', function(e){
if(e.keyCode == 32)
// Reloads name on spacebar
fetch("medlemsgrad.txt")
.then(handleErrors)
.then(response =>
response.text()
.then(text =>
document.getElementById("Noen").innerHTML = hvem(text))
)
.catch(error => console.log(error));
});
document.addEventListener("DOMContentLoaded", function(event) {
// Uses the built in fetch to read the textfile
fetch("medlemsgrad.txt")
.then(handleErrors)
.then(response =>
response.text()
.then(text =>
document.getElementById("Noen").innerHTML = hvem(text))
)
.catch(error => console.log(error));
});
function handleErrors(response) {
//If medlemsgrad.text not found set hvem to "Noen" and throw an error
if (!response.ok) {
document.getElementById("Noen").innerHTML = "Noen";
throw "medelmsgrad.txt is missing!";
}
return response;
}
function hvem(text) {
let lines = text.split(/\r\n|\n/);
let members = getMembers(lines);
let accumulativeMembers = getAccumulutiveMembers(members);
return getHvem(accumulativeMembers);
}
function getMembers(lines) {
// members = [[John Doe, 0.13], [Jane Roe, 0,23]]
let members = [];
lines.forEach(line => {
let data = line.split(',');
let membershipDegree = parseFloat(data[1]);
// This is to avoid the headers (navn, medlemsgrad),
// if membershipdegree is not a number skip
if (!isNaN(membershipDegree)) {
let name = data[0].trim();
members.push([name, membershipDegree]);
}
});
return members;
}
function getAccumulutiveMembers(members) {
// The next function normalizes the membershipDegree to 1 and order the
// members accumulatively. Example: Let
//
// [noen: 4, a: 1, b, 1]
//
// then the accumululative list looks like
//
// [noen: 4/6, a: 4/6 + 1/6, b: 4/6 + 1/6 + 1/6]
//
// [noen: 4/6, a: 5/6, b: 1]
//
// As it is sorted in ascending order
let totalMembershipDegree = 0;
members.forEach(member => {
totalMembershipDegree += member[1]
});
let accumulative = 0;
let accumulativeMemberlist = [];
members.forEach(member => {
name = member[0];
membershipDegree = member[1];
activity = parseFloat(membershipDegree) / totalMembershipDegree;
accumulative += activity;
accumulativeMemberlist.push([name, accumulative]);
});
// Sorts the accumulative list in ascending order (low to high)
accumulativeMemberlist.sort((a, b) => {
return a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0
});
// Sets the last member to 1, as the accumulative total should be 1
// it is not one due to slight round off errors
accumulativeMemberlist[accumulativeMemberlist.length - 1][1] = 1;
return accumulativeMemberlist;
}
function getHvem(accumulativeMembers) {
// Sets hvem as the default name. Tries 100 times to randomly pick someone
// from the accumulatively membership list (including noen)
// Example:
//
// [noen: 4/6, a: 5/6, b: 1]
//
// We then pick a random integer in the range [0, 1]
// If this random number is less than or equal to 4/6 then Noen is choosen.
// If the random number is between 4/6 and 5/6, a is choosen
// If the random number is between 5/6 and 1, b is choosen.
// This means that the chance of picking Noen is 4 times as great as b or a
// which is what we wanted.
console.log(accumulativeMembers);
let randInt = Math.random();
for (const member of accumulativeMembers) {
var name = member[0];
let number = member[1];
if (randInt <= number) {
return name;
}
};
return name;
}