Skip to content

Instantly share code, notes, and snippets.

@Meshiest
Last active October 22, 2017 20:08
Show Gist options
  • Save Meshiest/b872b81ba1c3da53591dbcf90c1c153e to your computer and use it in GitHub Desktop.
Save Meshiest/b872b81ba1c3da53591dbcf90c1c153e to your computer and use it in GitHub Desktop.
Basic auto grader for the first homework. Does not account for penalties
<!DOCTYPE html>
<html>
<head>
<title>HW1 Grader</title>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
body {
background-color: #f5f5f5;
font-family: sans-serif;
}
.container {
margin: 20px auto;
max-width: 800px;
}
.missing {
background-color: #fcc;
}
.criteria-header {
margin: 4px 0;
}
iframe {
background-color: #fff;
border: none;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
height: 150px;
margin: 8px;
max-width: 800px;
width: calc(100% - 16px);
}
.code-input {
border: none;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
margin-bottom: 16px;
min-height: 150px;
padding: 4px;
width: auto;
}
form, .criteria-container {
display: flex;
flex-direction: column;
margin: 8px;
width: auto;
}
.button {
background: #1E88E5;
border: none;
border-radius: 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
color: #fff;
cursor: pointer;
display: inline-block;
font-family: 'Roboto Condensed', sans-serif;
font-size: 1em;
margin: auto;
min-width: 100px;
padding: 4px;
text-align: center;
transition: all 0.5s;
}
.button:hover {
background: #2196F3;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
}
.button-row {
margin: auto;
}
.upload {
cursor: pointer;
position: relative;
}
.upload > input {
cursor: pointer;
height: 100%;
left: 0;
opacity: 0;
position: absolute;
top: 0;
width: 100%;
}
.note {
color: #999;
}
</style>
</head>
<body>
<div class="container">
<h1>Simple HW1 Grader (<a href="https://gist.github.com/Meshiest/8cdf5ab7c0ccbabaeb973f88338d9562">Rubric</a>)</h1>
<i id="header"></i>
<form id="form" action="javascript:void(0);">
<textarea placeholder="Assignment HTML"
class="code-input"
id="html-input"
name="html"></textarea>
<div class="button-row">
<button class="button" type="submit">Grade</button>
<div class="button upload">
Upload
<input type="file" id="upload" multiple>
</div>
</div>
</form>
<iframe id="frame"></iframe>
<div id="criteria" class="criteria-container"></div>
<i class="note">Note: this score does not account for penalties</i>
</div>
<script>
let total = 0;
// Adds a section header to the criteria list
function addCriteriaGroup(name) {
$('#criteria').append($('<h3 class="criteria-header"/>').text(name));
}
// Adds a header to the criteria list
function addCriteriaHeader(name) {
$('#criteria').append($('<h2 class="criteria-header"/>').text(name));
}
// Adds a note to the criteria list
function addCriteriaNote(name) {
$('#criteria').append($('<i/>').text(name));
}
// Called when the upload file button is pressed
async function uploadHTML(event) {
event.preventDefault();
let files = $('#upload')[0].files;
// Open first file locally
let file = files[0];
if(file && file.type === 'text/html')
setContent(file.name, await readFile(file));
// Open rest of the files in new tabs
for(let i = 1; i < files.length; i++) {
file = files[i];
if(file && file.type === 'text/html')
newTab(i, file, readFile(file));
}
}
$('#upload').on('change', uploadHTML);
// Reads a file, returns a promise
function readFile(file) {
return new Promise(resolve => {
let reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsText(file);
})
}
window.loadedHTML = {};
async function newTab(i, file, html) {
loadedHTML[file.name] = await html;
window.open(location.href + '#' + file.name, '_blank');
}
function setContent(name, html) {
console.log('set', name);
$('#header').text(name);
$('#html-input').val(html);
total = 0;
$('#criteria').empty();
$('button[type=submit]').click();
}
// Appends a check/cross followed by point value and the name of the criteria
function addCriteria(isDone, name, points, maxPoints) {
total += isDone ? points : 0;
$('#criteria').append($('<div class="criteria-entry"/>')
.addClass(!isDone ? 'missing' : '')
.html((isDone ? '&check; ' : '&cross; ') +
(isDone ? points + (maxPoints && maxPoints != points ? '/' + maxPoints : '') + 'pt' : '0/' + (points || maxPoints) + 'pt' ) + ' ' +
name));
}
// Checks if a selector is present
function assertTag(doc, selector) {
return $(doc).find(selector).length;
}
// Checks if any of the provided selectors are present
function assertTags(doc, selectors) {
for(var i = 0; i < selectors.length; i++)
if(assertTag(doc, selectors[i]))
return true;
return false;
}
// Checks if there is some pattern `pattern` in the style of `doc`
function assertCSS(doc, pattern) {
let inlineCheck = false;
$(doc).find('[style]').each((i,e) => {
if($(e).attr('style').match(pattern))
inlineCheck = true;
});
return $(doc).has('style') && $(doc).find('style').text().match(pattern) || inlineCheck;
}
// We have no internet somehow
if(typeof jQuery === 'undefined') {
document.body.innerHTML = "Missing jQuery. reloading in 5 seconds...";
setTimeout(() => location.reload(), 5000);
}
// Add our form submit callback
$(document).ready(() => {
// Setup our form submission
$('#form').submit(event => {
let form = event.target;
event.preventDefault();
let iframe = $('#frame')[0];
let doc = iframe.contentWindow.document;
window.doc = doc;
// Write to our iframe
doc.open();
doc.write(form.html.value);
doc.close();
// Clear previous test data
total = 0;
$('#criteria').empty();
// Base 10 points
if(form.html.value.length > 0)
total += 10;
// Rubric assertions and scoring
addCriteriaGroup('Tags');
addCriteria(assertTags(doc, ['ul', 'ol', 'dir']), 'ul/ol', 8);
if(assertTag(doc, 'li')) {
if(assertTags(doc, ['ul > li', 'ol > li', 'dir > li']))
addCriteria(true, 'ul > li', 8);
else
addCriteria(true, 'li', 4, 8);
} else {
addCriteria(false, 'li', 8);
}
if(assertTag(doc, 'dir'))
addCriteriaNote('The dir tag is not html5, but it does do the job.');
addCriteria(assertTag(doc, 'title'), 'title', 10);
addCriteria(assertTag(doc, 'table'), 'table', 10);
if(assertTag(doc, 'tr')) {
if(assertTags(doc, ['table > tr', 'tbody > tr', 'thead > tr']))
addCriteria(true, 'table > tr', 10);
else
addCriteria(true, 'tr', 5, 10);
} else {
addCriteria(false, 'tr', 10);
}
if(assertTag(doc, 'td')) {
if(assertTag(doc, 'tr > td'))
addCriteria(true, 'tr > td', 10);
else
addCriteria(true, 'td', 5, 10);
} else {
addCriteria(false, 'td', 10);
}
addCriteriaGroup('Attributes');
addCriteria(assertTags(doc, ['[rowspan]', '[colspan]']), 'rowspan/colspan attribute', 4);
addCriteriaGroup('Stylistic');
addCriteria(
assertTags(doc, ['h1', 'h2', 'h3']) ||
assertCSS(doc, 'font-size')
, 'header', 5);
addCriteria(
assertTag(doc, 'table[border]') ||
assertCSS(doc, 'border')
, 'table border', 5);
addCriteria(
assertTags(doc, ['b', 'strong', 'th', 'thead']) ||
assertCSS(doc, 'font-weight')
, 'bold text', 5);
addCriteria(
assertTag(doc, 'u') ||
assertCSS(doc, 'text-decoration')
, 'underlined text', 5);
// While align=middle isn't technically valid, it does work.
addCriteria(
assertTags(doc, ['[align=center]', '[align=middle]', 'th', 'center']) ||
assertCSS(doc, /text-align: *center/)
, 'centered text', 5);
if(assertTag(doc, '[align=middle]'))
addCriteriaNote('align=middle is not in the spec, but it does work.');
if(assertTag(doc, 'center'))
addCriteriaNote('The center tag is not html5, but it does do the job.');
addCriteria(
assertTags(doc, ['[align=right]', '[align=left]']) ||
assertCSS(doc, /text-align: *(right|left)/)
, 'right justified text', 5);
addCriteriaHeader('Total: ' + total + '/100pt');
});
// Load html if we were opened
if(window.opener && location.hash) {
let name = location.hash.slice(1);
let html = window.opener.loadedHTML[decodeURI(name)];
if(name && html)
setContent(name, html);
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment