A custom-built Chromebook exam system
This was a fun one and you can see the finished product for yourself at exam.alns.co.uk.
We needed a way for students who are entitled to use a laptop for their exams to be able to use their Chromebooks.
The caveats were:
- They were not allowed access to the public internet.
- They were not allowed any form of spell or grammar checking.
- They had to be able to save their work to USB.
In the past we had just used Windows laptops set to boot in to notepad with the proxy set to 127.0.0.1:80 as a simple way of doing this. But now, with every student having (or having access to) a Chromebook, we needed to make it work there.
We searched ages but could not find any way of meeting all three goals (without spending a fortune on overkill exam packages). So we decided to build our own!
The initial idea came from one of my colleagues and it started with a simple HTML page with just a text box on that they could write in.
Version 1 - the basics
Using the Google Workspace for Education admin console, we created 30 exam accounts and put them in their own OU for the below settings.
We then set the home page, new tab page and initial launch page to our exam site.
Next we blocked all sites except that our exam one, simply by putting a * in the "Blocked URLs" section and our exam URL in the "Blocked URL exceptions".
Also the text box/scroll bars were very clunky, having to scroll both the page and the text box.
Version 2 - Saving and polishing
We started off version 2 by adding in fields for date (auto-populated with that day's date by PHP and set to read-only), name and exam title.
To keep it neat, we put this in its own div, set to a fixed position, so it would always be at the top of your screen, even when you scrolled down. The new text boxes and the new save button went in this header div.
I popped my new CSS and JS into their own files to keep them compartmentalised.
Saving
I found a brilliant file saver script by Eli Gray that I imported to make it easier. It was then simply a short bit of Javascript to get all the data I wanted, make sure the name and title had been filled in and then save it as a text file named in the format date - name.txt. Students can then click the save button and download it to their Chromebook/USB drive:
//Save answer to file
var save = document.getElementById("downloadLink");
save.onclick = function() {
var saveName = document.getElementById('name').value;
var saveTitle = document.getElementById('examTitle').value;
var saveAnswer = document.getElementById('examAnswer').value;
var fileName = document.getElementById('date').value + ' - ' + saveName;
if (saveName == ""){
window.alert("You need to enter your name before saving.");
document.getElementById('name').focus();
} else if (saveTitle == "") {
window.alert("You need to enter the exam title before saving.") ;
document.getElementById('examTitle').focus();
} else {
var examAnswer = saveTitle + "\n" + saveName + "\n" +
document.getElementById('date').value + "\n\n" + saveAnswer;
saveTextAs(examAnswer, fileName+".txt");
}
Autosizing the text box
I wanted the textbox to automatically expand as the student typed, so they wouldn't end up with scroll bars for both the text box and the window. I found the brilliant autosize.js script by Jack Moore which was perfect for my needs!
All I had to do was import the .js file and then initialise it on my textbox in my Javascript under window.onload():
//Turn on autosize for examAnswer box
autosize(document.getElementById("examAnswer"));
It was as easy as that and worked perfectly!
Trying to stop the student accidentally closing/refreshing the tab
The next step was to pre-emptively stop the student accidentally closing or refreshing the tab, in which case they would lose any work they hadn't already saved as a .txt file on their USB.
I simply put in the below function. This gave the warning "Are you sure you want to navigate away from this page?" so they would have to deliberately chose to continue before they could lose any work. This wouldn't help them if the battery died or anything, but it was a start!
window.onbeforeunload = function(){
return "";
}
and that did the job for now.
Version 3 - the load button
After demoing version 2 to our SENCO (Special Educational Needs Coordinator - the person who is responsible for exam assistance/accessibility, among many other things) another issue/requirement came up.
Whilst exams would be sat in one session, the exam format had to be their "normal way of working" (as it wouldn't be fair to suddenly make them try to learn a new exam system on the actual day). However, some of the in-class assessments are carried out over multiple days/lessons at different times. So we needed a way for them to be able to carry on from previous work!
The easy answer would have been to tell them to open their .txt file and copy/paste the data into the answer field, but this introduced more potential for user error and some students might stuggle with the extra steps. So the answer was a load button!
I already knew the exact format the txt file would be in, so it was fairly easy to parse it into variables and then update the fields with the text.
I wanted the load button to give them a popup with a very obvious warning that it would overwrite anything they had already written, so I added in a little popup (controlled with jQuery) that would appear when you clicked the load button and disappear if you clicked anywhere outside the box (to keep it intuitive) or loaded a file.
//Load answer from file
var load = document.getElementById("loadLink");
load.onclick = function() {
$("#fileSelect").show(500);
}
$(document).click(function (e) {
if (!$(e.target).hasClass("button")
&& $(e.target).closest("#fileSelect").length === 0)
{
$("#fileSelect").hide(500);
}
});
document.getElementById("inputFile").addEventListener('change', getFile)
function getFile(event) {
const input = event.target
if ('files' in input && input.files.length > 0) {
placeFileContent(input.files[0])
}
}
function placeFileContent(file) {
target = document.getElementById('examAnswer');
readFileContent(file).then((textFile) => {
let content = textFile.split('\n');
document.getElementById("examTitle").value = content[0];
document.getElementById("name").value = content[1];
target.value = content.slice(4).join("");
autosize.update(target);
}).catch(error => console.log(error));
$("#fileSelect").hide(500);
}
function readFileContent(file) {
const reader = new FileReader()
return new Promise((resolve, reject) => {
reader.onload = event => resolve(event.target.result)
reader.onerror = error => reject(error)
reader.readAsText(file)
})
}
Success! Students could now load back in their files from previous lessons to continue.
Version 4 - autosave!
There was one more thing that wasn't quite perfect in my mind - there was still the chance of students losing work if they hit refresh and enter or even if the Chromebook battery died (extremely unlikely, but you couldn't say it was impossible and it would be a disaster if it happed during the actual exam).
In my mind, I was thinking about how Google Docs autosaves every time you add or delete a character.
I had already learned all about session storage for an earlier project (a "passport" where you received stamps for every subject you visited for our Virtual Open Evening last year) so it seemed like that would be a perfect way to achieve my goal.
This way, it would update the session storage every time they changed the text and if the Chromebook died or they closed the tab, the work would still be there when they came back. The only way they could lose it is if they logged off the Chromebook (as the exam accounts are set to ephemeral, so clear the profile at log off. This is actually a good thing as it means if another students logs on with the same exam account, it wouldn't automatically fill in another student's exam).
This actually turned out to be far easier that I thought!
I simply set up an "input" event listener on each text box which updated the appropriate session storage variable with the new contents of the textbox.
//Autosave data on text entry
document.getElementById("name").addEventListener('input', function(event){localStorage.savedName = event.target.value;});
document.getElementById("examTitle").addEventListener('input', function(event){localStorage.savedTitle = event.target.value;});
document.getElementById("examAnswer").addEventListener('input', function(event){localStorage.savedAnswer = event.target.value;});
Once this was done, all I had to do was check session storage during window.onload() and put their contents (if any) into the right text boxes.
Check for autosaved data on load
if (localStorage.savedName){
console.log("Loading saved name: "+localStorage.savedName);
document.getElementById("name").value = localStorage.savedName;
}
if (localStorage.savedTitle){
console.log("Loading saved title: "+localStorage.savedTitle);
document.getElementById("examTitle").value = localStorage.savedTitle;
}
if (localStorage.savedAnswer){
console.log("Loading saved answer: "+localStorage.savedAnswer);
document.getElementById("examAnswer").value = localStorage.savedAnswer;
}
That was it - I now had Google Docs-style autosaving!
Version 4 is where I have left it for now and it has already been used in four lessons successfully so far.
I would like to come back to it at some point, maybe to make it more responsive so it would be usable on a phone or tablet, not that that is needed right now.
From Caesars Illinois’ unbelievable a lot as} $1,250 on Caesars bonus to DraftKings Illinois’ familiar $50 risk-free guess and $1,050 deposit bonus, rewarding promos exist for the Illinois sports activities gambler to jump throughout. There’s never been a greater time for Illinois sports activities fans to rake within the winnings. One evening in 점보카지노 Eldoret, I joined him in his pink Toyota Vitz, a hatchback he had outfitted with tinted windows and electric-blue interior lighting. The Afrobeats blaring from the stereo would have been much less tinny if he nonetheless had his old sound system, he lamented, but he’d sold it to repay a mortgage used to put a guess.
ReplyDelete