Last active
October 22, 2017 20:08
-
-
Save Meshiest/b872b81ba1c3da53591dbcf90c1c153e to your computer and use it in GitHub Desktop.
Basic auto grader for the first homework. Does not account for penalties
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
<!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 ? '✓ ' : '✗ ') + | |
(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