changeset 2713:2dfc19a33bbc

Merge branch 'master' into vnext # Conflicts: # js/core.js # js/specification.js
author Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk>
date Fri, 14 Apr 2017 16:02:52 +0100
parents 549e2f97a401 (diff) b9efbbe0d829 (current diff)
children 52224b9d6e8b
files js/core.js js/specification.js
diffstat 11 files changed, 572 insertions(+), 718 deletions(-) [+]
line wrap: on
line diff
--- a/interfaces/AB.js	Thu Apr 13 11:36:24 2017 +0100
+++ b/interfaces/AB.js	Fri Apr 14 16:02:52 2017 +0100
@@ -1,6 +1,6 @@
 // Once this is loaded and parsed, begin execution
 loadInterface();
-
+/*globals window, interfaceContext, testState, Interface, audioEngineContext, console, document, specification, $, storage*/
 function loadInterface() {
     // Get the dimensions of the screen available to the page
     var width = window.innerWidth;
@@ -10,34 +10,6 @@
     // Custom comparator Object
     Interface.prototype.comparator = null;
 
-    Interface.prototype.checkScaleRange = function (min, max) {
-        var page = testState.getCurrentTestPage();
-        var audioObjects = audioEngineContext.audioObjects;
-        var state = true;
-        var str = "Please keep listening. ";
-        var minRanking = Infinity;
-        var maxRanking = -Infinity;
-        for (var ao of audioObjects) {
-            var rank = ao.interfaceDOM.getValue();
-            if (rank < minRanking) {
-                minRanking = rank;
-            }
-            if (rank > maxRanking) {
-                maxRanking = rank;
-            }
-        }
-        if (maxRanking * 100 < max) {
-            str += "At least one fragment must be selected."
-            state = false;
-        }
-        if (!state) {
-            console.log(str);
-            this.storeErrorNode(str);
-            interfaceContext.lightbox.post("Message", str);
-        }
-        return state;
-    }
-
     // The injection point into the HTML page
     interfaceContext.insertPoint = document.getElementById("topLevelBody");
     var testContent = document.createElement('div');
@@ -52,7 +24,7 @@
     titleSpan.id = "test-title";
 
     // Set title to that defined in XML, else set to default
-    if (titleAttr != undefined) {
+    if (titleAttr !== undefined) {
         titleSpan.textContent = titleAttr;
     } else {
         titleSpan.textContent = 'Listening test';
@@ -63,7 +35,8 @@
     var pagetitle = document.createElement('div');
     pagetitle.className = "pageTitle";
     pagetitle.align = "center";
-    var titleSpan = document.createElement('span');
+
+    titleSpan = document.createElement('span');
     titleSpan.id = "pageTitle";
     pagetitle.appendChild(titleSpan);
 
@@ -150,10 +123,10 @@
 
     // Set the page title
     if (typeof audioHolderObject.title == "string" && audioHolderObject.title.length > 0) {
-        document.getElementById("test-title").textContent = audioHolderObject.title
+        document.getElementById("test-title").textContent = audioHolderObject.title;
     }
 
-    if (interfaceObj.title != null) {
+    if (interfaceObj.title !== null) {
         document.getElementById("pageTitle").textContent = interfaceObj.title;
     }
 
@@ -182,7 +155,7 @@
             switch (option.name) {
                 case "playhead":
                     var playbackHolder = document.getElementById('playback-holder');
-                    if (playbackHolder == null) {
+                    if (playbackHolder === null) {
                         playbackHolder = document.createElement('div');
                         playbackHolder.id = 'playback-holder';
                         playbackHolder.style.width = "100%";
@@ -194,7 +167,7 @@
                     break;
                 case "page-count":
                     var pagecountHolder = document.getElementById('page-count');
-                    if (pagecountHolder == null) {
+                    if (pagecountHolder === null) {
                         pagecountHolder = document.createElement('div');
                         pagecountHolder.id = 'page-count';
                         document.getElementById('interface-buttons').appendChild(pagecountHolder);
@@ -202,7 +175,7 @@
                     pagecountHolder.innerHTML = '<span>Page ' + (testState.stateIndex + 1) + ' of ' + testState.stateMap.length + '</span>';
                     break;
                 case "volume":
-                    if (document.getElementById('master-volume-holder-float') == null) {
+                    if (document.getElementById('master-volume-holder-float') === null) {
                         feedbackHolder.appendChild(interfaceContext.volume.object);
                     }
                     break;
@@ -246,51 +219,50 @@
         this.playback.textContent = "Listen";
         this.box.appendChild(this.selector);
         this.box.appendChild(this.playback);
-        this.selector.onclick = function (event) {
+        this.selectorClicked = function () {
+            var i;
             var time = audioEngineContext.timer.getTestTime();
-            if ($(event.currentTarget).hasClass('disabled')) {
+            if (this.parent.state !== 1) {
+                interfaceContext.lightbox.post("Message", "Please wait for the sample to load");
                 console.log("Please wait until sample has loaded");
                 return;
             }
-            if (audioEngineContext.status == 0) {
+            if (audioEngineContext.status === 0) {
                 interfaceContext.lightbox.post("Message", "Please listen to the samples before making a selection");
                 console.log("Please listen to the samples before making a selection");
                 return;
             }
-            var id = event.currentTarget.parentElement.getAttribute('track-id');
-            interfaceContext.comparator.selected = id;
-            if ($(event.currentTarget).hasClass("selected")) {
-                $(".comparator-selector").removeClass('selected');
-                for (var i = 0; i < interfaceContext.comparator.comparators.length; i++) {
-                    var obj = interfaceContext.comparator.comparators[i];
-                    obj.parent.metric.moved(time, 0);
-                    obj.value = 0;
+            interfaceContext.comparator.selected = this.id;
+            $(".comparator-selector").removeClass('selected');
+            $(this.selector).addClass('selected');
+            this.comparator.comparators.forEach(function (a) {
+                if (a !== this) {
+                    a.value = 0;
+                } else {
+                    a.value = 1;
                 }
-            } else {
-                $(".comparator-selector").removeClass('selected');
-                $(event.currentTarget).addClass('selected');
-                for (var i = 0; i < interfaceContext.comparator.comparators.length; i++) {
-                    var obj = interfaceContext.comparator.comparators[i];
-                    if (i == id) {
-                        obj.value = 1;
-                    } else {
-                        obj.value = 0;
-                    }
-                    obj.parent.metric.moved(time, obj.value);
-                }
-                console.log("Selected " + id + ' (' + time + ')');
-            }
+                a.parent.metric.moved(time, a.value);
+            }, this);
+            console.log("Selected " + this.id + ' (' + time + ')');
         };
         this.playback.setAttribute("playstate", "ready");
-        this.playback.onclick = function (event) {
-            var id = event.currentTarget.parentElement.getAttribute('track-id');
-            if (event.currentTarget.getAttribute("playstate") == "ready") {
-                audioEngineContext.play(id);
-            } else if (event.currentTarget.getAttribute("playstate") == "playing") {
+        this.playbackClicked = function () {
+            if (this.playback.getAttribute("playstate") == "ready") {
+                audioEngineContext.play(this.id);
+            } else if (this.playback.getAttribute("playstate") == "playing") {
                 audioEngineContext.stop();
             }
 
         };
+        this.handleEvent = function (event) {
+            if (event.currentTarget === this.selector) {
+                this.selectorClicked();
+            } else if (event.currentTarget === this.playback) {
+                this.playbackClicked();
+            }
+        }
+        this.playback.addEventListener("click", this);
+        this.selector.addEventListener("click", this);
 
         this.enable = function () {
             if (this.parent.state == 1) {
@@ -311,7 +283,7 @@
             // audioObject has an error!!
             this.playback.textContent = "Error";
             $(this.playback).addClass("error-colour");
-        }
+        };
         this.startPlayback = function () {
             if (this.parent.specification.parent.playOne || specification.playOne) {
                 $('.comparator-button').text('Wait');
@@ -369,6 +341,11 @@
                 label = interfaceContext.getLabel(labelType, index, audioHolderObject.labelStart);
             }
             var node = new this.comparatorBox(audioObject, index, label);
+            Object.defineProperties(node, {
+                'comparator': {
+                    'value': this
+                }
+            });
             audioObject.bindInterface(node);
             this.comparators.push(node);
             this.boxHolders.appendChild(node.box);
@@ -392,7 +369,7 @@
     document.getElementById('box-holders').style.width = boxW + 'px';
 
     var outsideRef = document.getElementById('outside-reference');
-    if (outsideRef != null) {
+    if (outsideRef !== null) {
         outsideRef.style.left = (window.innerWidth - 120) / 2 + 'px';
     }
 }
@@ -403,41 +380,39 @@
 
     for (var i = 0; i < checks.length; i++) {
         if (checks[i].type == 'check') {
+            var checkState;
             switch (checks[i].name) {
                 case 'fragmentPlayed':
                     // Check if all fragments have been played
-                    var checkState = interfaceContext.checkAllPlayed();
-                    if (checkState == false) {
+                    checkState = interfaceContext.checkAllPlayed();
+                    if (checkState === false) {
                         canContinue = false;
                     }
                     break;
                 case 'fragmentFullPlayback':
                     // Check all fragments have been played to their full length
-                    var checkState = interfaceContext.checkFragmentsFullyPlayed();
-                    if (checkState == false) {
+                    checkState = interfaceContext.checkFragmentsFullyPlayed();
+                    if (checkState === false) {
                         canContinue = false;
                     }
                     break;
                 case 'fragmentMoved':
                     // Check all fragment sliders have been moved.
-                    var checkState = interfaceContext.checkAllMoved();
-                    if (checkState == false) {
+                    checkState = interfaceContext.checkAllMoved();
+                    if (checkState === false) {
                         canContinue = false;
                     }
                     break;
                 case 'fragmentComments':
                     // Check all fragment sliders have been moved.
-                    var checkState = interfaceContext.checkAllCommented();
-                    if (checkState == false) {
+                    checkState = interfaceContext.checkAllCommented();
+                    if (checkState === false) {
                         canContinue = false;
                     }
                     break;
                 case 'scalerange':
                     // Check the scale has been used effectively
-                    var checkState = interfaceContext.checkScaleRange(checks[i].min, checks[i].max);
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    console.log("WARNING - Check 'scalerange' does not make sense in AB/ABX! Ignoring!");
                     break;
                 default:
                     console.log("WARNING - Check option " + checks[i].check + " is not supported on this interface");
@@ -455,7 +430,7 @@
             playback.click();
             // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
         } else {
-            if (audioEngineContext.timer.testStarted == false) {
+            if (audioEngineContext.timer.testStarted === false) {
                 interfaceContext.lightbox.post("Warning", 'You have not started the test! Please click play on a sample to begin the test!');
                 return;
             }
--- a/interfaces/ABX.js	Thu Apr 13 11:36:24 2017 +0100
+++ b/interfaces/ABX.js	Fri Apr 14 16:02:52 2017 +0100
@@ -4,6 +4,7 @@
  */
 
 // Once this is loaded and parsed, begin execution
+/* globals interfaceContext, Interface, testState, audioEngineContext, console, document, window, feedbackHolder, $, specification, storage*/
 loadInterface();
 
 function loadInterface() {
@@ -12,34 +13,6 @@
 
     interfaceContext.insertPoint.innerHTML = ""; // Clear the current schema
 
-    Interface.prototype.checkScaleRange = function (min, max) {
-        var page = testState.getCurrentTestPage();
-        var audioObjects = audioEngineContext.audioObjects;
-        var state = true;
-        var str = "Please keep listening. ";
-        var minRanking = Infinity;
-        var maxRanking = -Infinity;
-        for (var ao of audioObjects) {
-            var rank = ao.interfaceDOM.getValue();
-            if (rank < minRanking) {
-                minRanking = rank;
-            }
-            if (rank > maxRanking) {
-                maxRanking = rank;
-            }
-        }
-        if (maxRanking * 100 < max) {
-            str += "At least one fragment must be selected."
-            state = false;
-        }
-        if (!state) {
-            console.log(str);
-            this.storeErrorNode(str);
-            interfaceContext.lightbox.post("Message", str);
-        }
-        return state;
-    }
-
     // Custom comparator Object
     Interface.prototype.comparator = null;
 
@@ -57,7 +30,7 @@
     titleSpan.id = "test-title";
 
     // Set title to that defined in XML, else set to default
-    if (titleAttr != undefined) {
+    if (titleAttr !== undefined) {
         titleSpan.textContent = titleAttr;
     } else {
         titleSpan.textContent = 'Listening test';
@@ -68,7 +41,8 @@
     var pagetitle = document.createElement('div');
     pagetitle.className = "pageTitle";
     pagetitle.align = "center";
-    var titleSpan = document.createElement('span');
+
+    titleSpan = document.createElement('span');
     titleSpan.id = "pageTitle";
     pagetitle.appendChild(titleSpan);
 
@@ -131,7 +105,7 @@
     // Load the full interface
     testState.initialise();
     testState.advanceState();
-};
+}
 
 function loadTest(page) {
     // Called each time a new test page is to be build. The page specification node is the only item passed in
@@ -148,10 +122,10 @@
 
     // Set the page title
     if (typeof page.title == "string" && page.title.length > 0) {
-        document.getElementById("test-title").textContent = page.title
+        document.getElementById("test-title").textContent = page.title;
     }
 
-    if (interfaceObj.title != null) {
+    if (interfaceObj.title !== null) {
         document.getElementById("pageTitle").textContent = interfaceObj.title;
     }
 
@@ -163,7 +137,7 @@
             switch (option.name) {
                 case "playhead":
                     var playbackHolder = document.getElementById('playback-holder');
-                    if (playbackHolder == null) {
+                    if (playbackHolder === null) {
                         playbackHolder = document.createElement('div');
                         playbackHolder.style.width = "100%";
                         playbackHolder.style.float = "left";
@@ -174,7 +148,7 @@
                     break;
                 case "page-count":
                     var pagecountHolder = document.getElementById('page-count');
-                    if (pagecountHolder == null) {
+                    if (pagecountHolder === null) {
                         pagecountHolder = document.createElement('div');
                         pagecountHolder.id = 'page-count';
                     }
@@ -183,7 +157,7 @@
                     inject.appendChild(pagecountHolder);
                     break;
                 case "volume":
-                    if (document.getElementById('master-volume-holder') == null) {
+                    if (document.getElementById('master-volume-holder') === null) {
                         feedbackHolder.appendChild(interfaceContext.volume.object);
                     }
                     break;
@@ -231,60 +205,54 @@
         this.playback.textContent = "Listen";
         this.box.appendChild(this.selector);
         this.box.appendChild(this.playback);
-        this.selector.onclick = function (event) {
-            var label = event.currentTarget.children[0].textContent;
+        this.selectorClicked = function (event) {
             if (label == "X" || label == "x") {
                 return;
             }
             var time = audioEngineContext.timer.getTestTime();
-            if ($(event.currentTarget).hasClass('disabled')) {
+            if (this.disabled) {
+                interfaceContext.lightbox.post("Message", "Please wait until sample has loaded");
                 console.log("Please wait until sample has loaded");
                 return;
             }
-            if (audioEngineContext.status == 0) {
+            if (audioEngineContext.status === 0) {
                 interfaceContext.lightbox.post("Message", "Please listen to the samples before making a selection");
                 console.log("Please listen to the samples before making a selection");
                 return;
             }
-            var id = event.currentTarget.parentElement.getAttribute('track-id');
-            interfaceContext.comparator.selected = id;
-            if ($(event.currentTarget).hasClass("selected")) {
-                $(".comparator-selector").removeClass('selected');
-                for (var i = 0; i < interfaceContext.comparator.pair.length; i++) {
-                    var obj = interfaceContext.comparator.pair[i];
-                    obj.parent.metric.moved(time, 0);
-                    obj.value = 0;
-                }
-            } else {
-                $(".comparator-selector").removeClass('selected');
-                $(event.currentTarget).addClass('selected');
-                for (var i = 0; i < interfaceContext.comparator.pair.length; i++) {
-                    var obj = interfaceContext.comparator.pair[i];
-                    if (i == id) {
-                        obj.value = 1;
-                    } else {
-                        obj.value = 0;
-                    }
-                    obj.parent.metric.moved(time, obj.value);
-                }
-                console.log("Selected " + id + ' (' + time + ')');
-            }
+            interfaceContext.comparator.selected = this.id;
+            $(".comparator-selector").removeClass('selected');
+            $(this.selector).addClass('selected');
+            interfaceContext.comparator.pair.forEach(function (obj) {
+                obj.value = 1.0 * obj === this;
+                obj.parent.metric.moved(time, obj.value);
+            });
+            console.log("Selected " + this.id + ' (' + time + ')');
         };
         this.playback.setAttribute("playstate", "ready");
-        this.playback.onclick = function (event) {
-            var id = event.currentTarget.parentElement.getAttribute('track-id');
-            if (event.currentTarget.getAttribute("playstate") == "ready") {
-                audioEngineContext.play(id);
+        this.playbackClicked = function (event) {
+            if (this.playback.getAttribute("playstate") == "ready") {
+                audioEngineContext.play(this.id);
             } else if (event.currentTarget.getAttribute("playstate") == "playing") {
                 audioEngineContext.stop();
             }
 
         };
+        this.handleEvent = function (event) {
+            if (event.currentTarget === this.playback) {
+                this.playbackClicked(event);
+            } else if (event.currentTarget === this.selector) {
+                this.selectorClicked(event);
+            }
+        };
+        this.playback.addEventListener("click", this);
+        this.selector.addEventListener("click", this);
         this.enable = function () {
             // This is used to tell the interface object that playback of this node is ready
             if (this.parent.state == 1) {
                 $(this.selector).removeClass('disabled');
                 this.playback.disabled = false;
+                this.disabled = false;
             }
         };
         this.updateLoading = function (progress) {
@@ -345,7 +313,7 @@
         };
         this.error = function () {
             // If there is an error with the audioObject, this will be called to indicate a failure
-        }
+        };
     };
     // Ensure there are only two comparisons per page
     if (page.audioElements.length != 2) {
@@ -353,31 +321,42 @@
         return;
     }
     // Build the three audio elements
+
+    function buildElement(index, audioObject) {
+        var label;
+        switch (index) {
+            case 0:
+                label = "A";
+                break;
+            case 1:
+                label = "B";
+                break;
+            default:
+                label = "X";
+                break;
+        }
+        var node = new this.interfaceObject(audioObject, label);
+        audioObject.bindInterface(node);
+        return node;
+    }
+
     this.pair = [];
     this.X = null;
     this.boxHolders = document.getElementById('box-holders');
-    for (var index = 0; index < page.audioElements.length; index++) {
-        var element = page.audioElements[index];
+    var node;
+    page.audioElements.forEach(function (element, index) {
         if (element.type != 'normal') {
             console.log("WARNING - ABX can only have normal elements. Page " + page.id + ", Element " + element.id);
             element.type = "normal";
         }
-        var audioObject = audioEngineContext.newTrack(element);
-        var label;
-        if (index == 0) {
-            label = "A";
-        } else {
-            label = "B";
-        }
-        var node = new this.interfaceObject(audioObject, label);
-        audioObject.bindInterface(node);
+        node = buildElement.call(this, index, audioEngineContext.newTrack(element));
         this.pair.push(node);
         this.boxHolders.appendChild(node.box);
-    }
+    }, this);
     var elementId = Math.floor(Math.random() * 2); //Randomly pick A or B to be X
     var element = new page.audioElementNode(specification);
     for (var atr in page.audioElements[elementId]) {
-        eval("element." + atr + " = page.audioElements[elementId]." + atr);
+        element[atr] = page.audioElements[elementId][atr];
     }
     element.id += "-X";
     if (typeof element.name == "string") {
@@ -397,17 +376,9 @@
     aeNode.appendChild(storage.document.createElement('metric'));
     root.appendChild(aeNode);
     // Build the 'X' element
+    var label;
     var audioObject = audioEngineContext.newTrack(element);
-    var label;
-    switch (audioObject.specification.parent.label) {
-        case "letter":
-            label = "x";
-            break;
-        default:
-            label = "X";
-            break;
-    }
-    var node = new this.interfaceObject(audioObject, label);
+    node = buildElement.call(this, 3, audioObject);
     node.box.children[0].classList.add('inactive');
     audioObject.bindInterface(node);
     this.X = node;
@@ -434,42 +405,28 @@
         canContinue = true;
 
     for (var i = 0; i < checks.length; i++) {
+        var checkState = true;
         if (checks[i].type == 'check') {
             switch (checks[i].name) {
                 case 'fragmentPlayed':
                     // Check if all fragments have been played
-                    var checkState = interfaceContext.checkAllPlayed();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllPlayed();
+
                     break;
                 case 'fragmentFullPlayback':
                     // Check all fragments have been played to their full length
-                    var checkState = interfaceContext.checkFragmentsFullyPlayed();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkFragmentsFullyPlayed();
                     break;
                 case 'fragmentMoved':
                     // Check all fragment sliders have been moved.
-                    var checkState = interfaceContext.checkAllMoved();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllMoved();
                     break;
                 case 'fragmentComments':
                     // Check all fragment sliders have been moved.
-                    var checkState = interfaceContext.checkAllCommented();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
                     break;
                 case 'scalerange':
                     // Check the scale has been used effectively
-                    var checkState = interfaceContext.checkScaleRange(checks[i].min, checks[i].max);
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    console.log("WARNING - Check 'scalerange' does not make sense in AB/ABX! Ignoring!");
                     break;
                 default:
                     console.log("WARNING - Check option " + checks[i].check + " is not supported on this interface");
@@ -477,17 +434,19 @@
             }
 
         }
-        if (!canContinue) {
+        if (checkState === false) {
+            canContinue = false;
             break;
         }
     }
+
     if (canContinue) {
         if (audioEngineContext.status == 1) {
             var playback = document.getElementById('playback-button');
             playback.click();
             // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
         } else {
-            if (audioEngineContext.timer.testStarted == false) {
+            if (audioEngineContext.timer.testStarted === false) {
                 interfaceContext.lightbox.post("Warning", 'You have not started the test! Please listen to a sample to begin the test!');
                 return;
             }
--- a/interfaces/ape.js	Thu Apr 13 11:36:24 2017 +0100
+++ b/interfaces/ape.js	Fri Apr 14 16:02:52 2017 +0100
@@ -3,7 +3,8 @@
  *  Create the APE interface
  */
 
-
+/*globals window,interfaceContext, document, audioEngineContext, console, $, Interface, testState, storage, specification */
+/*globals metricTracker */
 // Once this is loaded and parsed, begin execution
 loadInterface();
 
@@ -21,7 +22,7 @@
 
     // Bindings for interfaceContext
     interfaceContext.checkAllPlayed = function () {
-        hasBeenPlayed = audioEngineContext.checkAllPlayed();
+        var hasBeenPlayed = audioEngineContext.checkAllPlayed();
         if (hasBeenPlayed.length > 0) // if a fragment has not been played yet
         {
             var str = "";
@@ -53,14 +54,14 @@
             var interfaceTID = [];
             for (var j = 0; j < this.interfaceSliders[i].metrics.length; j++) {
                 var ao_id = this.interfaceSliders[i].sliders[j].getAttribute("trackIndex");
-                if (this.interfaceSliders[i].metrics[j].wasMoved == false && audioEngineContext.audioObjects[ao_id].interfaceDOM.canMove()) {
+                if (this.interfaceSliders[i].metrics[j].wasMoved === false && audioEngineContext.audioObjects[ao_id].interfaceDOM.canMove()) {
                     state = false;
                     interfaceTID.push(j);
                 }
             }
-            if (interfaceTID.length != 0) {
+            if (interfaceTID.length !== 0) {
                 var interfaceName = this.interfaceSliders[i].interfaceObject.title;
-                if (interfaceName == undefined) {
+                if (interfaceName === undefined) {
                     str += 'On axis ' + String(i + 1) + ' you must move ';
                 } else {
                     str += 'On axis "' + interfaceName + '" you must move ';
@@ -76,7 +77,7 @@
                 }
             }
         }
-        if (state != true) {
+        if (state !== true) {
             this.storeErrorNode(str);
             interfaceContext.lightbox.post("Message", str);
             console.log(str);
@@ -84,75 +85,38 @@
         return state;
     };
 
-    Interface.prototype.checkAllCommented = function () {
+    interfaceContext.checkScaleRange = function () {
         var audioObjs = audioEngineContext.audioObjects;
         var audioHolder = testState.stateMap[testState.stateIndex];
-        var state = true;
-        if (audioHolder.elementComments) {
-            var strNums = [];
-            for (var i = 0; i < audioObjs.length; i++) {
-                if (audioObjs[i].commentDOM.trackCommentBox.value.length == 0) {
-                    state = false;
-                    strNums.push(i);
-                }
-            }
-            if (state == false) {
-                var str = "";
-                if (strNums.length > 1) {
-
-                    for (var i = 0; i < strNums.length; i++) {
-                        var ao_id = audioEngineContext.audioObjects[strNums[i]].interfaceDOM.getPresentedId();
-                        str = str + (ao_id); // start from 1
-                        if (i < strNums.length - 2) {
-                            str += ", ";
-                        } else if (i == strNums.length - 2) {
-                            str += " or ";
-                        }
-                    }
-                    str = 'You have not commented on fragments ' + str + ' yet. Please listen, rate and comment all samples before submitting.';
-                } else {
-                    str = 'You have not commented on fragment ' + (audioEngineContext.audioObjects[strNums[0]].interfaceDOM.getPresentedId()) + ' yet. Please listen, rate and comment all samples before submitting.';
-                }
-                this.storeErrorNode(str);
-                interfaceContext.lightbox.post("Message", str);
-                console.log(str);
-            }
-        }
-        return state;
-    };
-
-    Interface.prototype.checkScaleRange = function () {
-        var audioObjs = audioEngineContext.audioObjects;
-        var audioHolder = testState.stateMap[testState.stateIndex];
+        var interfaceObject = this.interfaceSliders[0].interfaceObject;
         var state = true;
         var str = '';
-        for (var i = 0; i < this.interfaceSliders.length; i++) {
-            var minScale;
-            var maxScale;
-            var interfaceObject = interfaceContext.interfaceSliders[0].interfaceObject;
-            for (var j = 0; j < interfaceObject.options.length; j++) {
-                if (interfaceObject.options[j].check == "scalerange") {
-                    minScale = interfaceObject.options[j].min;
-                    maxScale = interfaceObject.options[j].max;
-                    break;
+        this.interfaceSliders.forEach(function (sliderHolder, i) {
+            var scales = (function () {
+                var scaleRange = interfaceObject.options.find(function (a) {
+                    return a.name == "scalerange";
+                });
+                return {
+                    min: scaleRange.min,
+                    max: scaleRange.max
+                };
+            })();
+            var range = sliderHolder.sliders.reduce(function (a, b) {
+                var v = convSliderPosToRate(b) * 100.0;
+                return {
+                    min: Math.min(a.min, v),
+                    max: Math.max(a.max, v)
                 }
+            }, {
+                min: 100,
+                max: 0
+            });
+            if (range.min >= scales.min || range.max <= scales.max) {
+                state = false;
+                str += 'On axis "' + sliderHolder.interfaceObject.title + '" you have not used the full width of the scale. ';
             }
-            var minRanking = convSliderPosToRate(this.interfaceSliders[i].sliders[0]);
-            var maxRanking = minRanking;
-            for (var j = 1; j < this.interfaceSliders[i].sliders.length; j++) {
-                var ranking = convSliderPosToRate(this.interfaceSliders[i].sliders[j]);
-                if (ranking < minRanking) {
-                    minRanking = ranking;
-                } else if (ranking > maxRanking) {
-                    maxRanking = ranking;
-                }
-            }
-            if (minRanking > minScale || maxRanking < maxScale) {
-                state = false;
-                str += 'On axis "' + this.interfaceSliders[i].interfaceObject.title + '" you have not used the full width of the scale. ';
-            }
-        }
-        if (state != true) {
+        });
+        if (state !== true) {
             this.storeErrorNode(str);
             interfaceContext.lightbox.post("Message", str);
             console.log(str);
@@ -163,13 +127,13 @@
     Interface.prototype.objectSelected = null;
     Interface.prototype.objectMoved = false;
     Interface.prototype.selectObject = function (object) {
-        if (this.objectSelected == null) {
+        if (this.objectSelected === null) {
             this.objectSelected = object;
             this.objectMoved = false;
         }
     };
     Interface.prototype.moveObject = function () {
-        if (this.objectMoved == false) {
+        if (this.objectMoved === false) {
             this.objectMoved = true;
         }
     };
@@ -198,7 +162,7 @@
     titleSpan.id = "test-title";
 
     // Set title to that defined in XML, else set to default
-    if (titleAttr != undefined) {
+    if (titleAttr !== undefined) {
         titleSpan.textContent = titleAttr;
     } else {
         titleSpan.textContent = 'Listening test';
@@ -274,12 +238,12 @@
     sliderHolder.innerHTML = "";
 
     // Set labelType if default to number
-    if (audioHolderObject.label == "default" || audioHolderObject.label == "") {
+    if (audioHolderObject.label === "default" || audioHolderObject.label === "") {
         audioHolderObject.label = "number";
     }
     // Set the page title
     if (typeof audioHolderObject.title == "string" && audioHolderObject.title.length > 0) {
-        document.getElementById("test-title").textContent = audioHolderObject.title
+        document.getElementById("test-title").textContent = audioHolderObject.title;
     }
 
 
@@ -287,17 +251,17 @@
     document.getElementById("outside-reference-holder").innerHTML = "";
 
     var interfaceObj = interfaceContext.getCombinedInterfaces(audioHolderObject);
-    for (var k = 0; k < interfaceObj.length; k++) {
+    interfaceObj.forEach(function (interfaceObjectInstance) {
         // Create the div box to center align
-        interfaceContext.interfaceSliders.push(new interfaceSliderHolder(interfaceObj[k]));
-    }
+        interfaceContext.interfaceSliders.push(new interfaceSliderHolder(interfaceObjectInstance));
+    });
     interfaceObj.forEach(function (interface) {
-        for (var option of interface.options) {
+        interface.options.forEach(function (option) {
             if (option.type == "show") {
                 switch (option.name) {
                     case "playhead":
                         var playbackHolder = document.getElementById('playback-holder');
-                        if (playbackHolder == null) {
+                        if (playbackHolder === null) {
                             playbackHolder = document.createElement('div');
                             playbackHolder.style.width = "100%";
                             playbackHolder.align = 'center';
@@ -307,7 +271,7 @@
                         break;
                     case "page-count":
                         var pagecountHolder = document.getElementById('page-count');
-                        if (pagecountHolder == null) {
+                        if (pagecountHolder === null) {
                             pagecountHolder = document.createElement('div');
                             pagecountHolder.id = 'page-count';
                         }
@@ -316,7 +280,7 @@
                         inject.appendChild(pagecountHolder);
                         break;
                     case "volume":
-                        if (document.getElementById('master-volume-holder') == null) {
+                        if (document.getElementById('master-volume-holder') === null) {
                             feedbackHolder.appendChild(interfaceContext.volume.object);
                         }
                         break;
@@ -325,7 +289,7 @@
                         break;
                 }
             }
-        }
+        });
     });
 
     var commentBoxPrefix = "Comment on fragment";
@@ -334,7 +298,7 @@
 
     var loopPlayback = audioHolderObject.loop;
 
-    currentTestHolder = document.createElement('audioHolder');
+    var currentTestHolder = document.createElement('audioHolder');
     currentTestHolder.id = audioHolderObject.id;
     currentTestHolder.repeatCount = audioHolderObject.repeatCount;
 
@@ -372,7 +336,7 @@
     $('.slider').mousemove(function (event) {
         event.preventDefault();
         var obj = interfaceContext.getSelectedObject();
-        if (obj == null) {
+        if (obj === null) {
             return;
         }
         var move = event.clientX - 6;
@@ -386,7 +350,7 @@
     $('.slider').on('touchmove', null, function (event) {
         event.preventDefault();
         var obj = interfaceContext.getSelectedObject();
-        if (obj == null) {
+        if (obj === null) {
             return;
         }
         var move = event.originalEvent.targetTouches[0].clientX - 6;
@@ -400,14 +364,15 @@
     $(document).mouseup(function (event) {
         event.preventDefault();
         var obj = interfaceContext.getSelectedObject();
-        if (obj == null) {
+        if (obj === null) {
             return;
         }
         var interfaceID = obj.parentElement.getAttribute("interfaceid");
         var trackID = obj.getAttribute("trackindex");
-        if (interfaceContext.hasSelectedObjectMoved() == true) {
+        var id;
+        if (interfaceContext.hasSelectedObjectMoved() === true) {
             var l = $(obj).css("left");
-            var id = obj.getAttribute('trackIndex');
+            id = obj.getAttribute('trackIndex');
             var time = audioEngineContext.timer.getTestTime();
             var rate = convSliderPosToRate(obj);
             audioEngineContext.audioObjects[id].metric.moved(time, rate);
@@ -415,7 +380,7 @@
             console.log("slider " + id + " moved to " + rate + ' (' + time + ')');
             obj.setAttribute("slider-value", convSliderPosToRate(obj));
         } else {
-            var id = Number(obj.attributes['trackIndex'].value);
+            id = Number(obj.attributes.trackIndex.value);
             //audioEngineContext.metric.sliderPlayed(id);
             audioEngineContext.play(id);
         }
@@ -424,12 +389,12 @@
 
     $('.slider').on('touchend', null, function (event) {
         var obj = interfaceContext.getSelectedObject();
-        if (obj == null) {
+        if (obj === null) {
             return;
         }
         var interfaceID = obj.parentElement.getAttribute("interfaceid");
         var trackID = obj.getAttribute("trackindex");
-        if (interfaceContext.hasSelectedObjectMoved() == true) {
+        if (interfaceContext.hasSelectedObjectMoved() === true) {
             var l = $(obj).css("left");
             var id = obj.getAttribute('trackIndex');
             var time = audioEngineContext.timer.getTestTime();
@@ -446,7 +411,7 @@
         for (var i = 0; i < interfaceList[k].options.length; i++) {
             if (interfaceList[k].options[i].type == 'show' && interfaceList[k].options[i].name == 'playhead') {
                 var playbackHolder = document.getElementById('playback-holder');
-                if (playbackHolder == null) {
+                if (playbackHolder === null) {
                     playbackHolder = document.createElement('div');
                     playbackHolder.id = "playback-holder";
                     playbackHolder.style.width = "100%";
@@ -456,7 +421,7 @@
                 }
             } else if (interfaceList[k].options[i].type == 'show' && interfaceList[k].options[i].name == 'page-count') {
                 var pagecountHolder = document.getElementById('page-count');
-                if (pagecountHolder == null) {
+                if (pagecountHolder === null) {
                     pagecountHolder = document.createElement('div');
                     pagecountHolder.id = 'page-count';
                 }
@@ -464,7 +429,7 @@
                 var inject = document.getElementById('interface-buttons');
                 inject.appendChild(pagecountHolder);
             } else if (interfaceList[k].options[i].type == 'show' && interfaceList[k].options[i].name == 'volume') {
-                if (document.getElementById('master-volume-holder') == null) {
+                if (document.getElementById('master-volume-holder') === null) {
                     feedbackHolder.appendChild(interfaceContext.volume.object);
                 }
             } else if (interfaceList[k].options[i].type == 'show' && interfaceList[k].options[i].name == 'comments') {
@@ -497,7 +462,7 @@
     pagetitle.align = "center";
     var titleSpan = document.createElement('span');
     titleSpan.id = "pageTitle-" + this.id;
-    if (interfaceObject.title != undefined && typeof interfaceObject.title == "string") {
+    if (interfaceObject.title !== undefined && typeof interfaceObject.title == "string") {
         titleSpan.textContent = interfaceObject.title;
     } else {
         titleSpan.textContent = "Axis " + String(this.id + 1);
@@ -507,7 +472,7 @@
 
     // Create the slider box to hold the slider elements
     this.canvas = document.createElement('div');
-    if (this.name != undefined)
+    if (this.name !== undefined)
         this.canvas.id = 'slider-' + this.name;
     else
         this.canvas.id = 'slider-' + this.id;
@@ -531,16 +496,16 @@
     var positionScale = this.canvas.style.width.substr(0, this.canvas.style.width.length - 2);
     var offset = 50;
     var dest = document.getElementById("slider-holder").appendChild(this.sliderDOM);
-    for (var scaleObj of interfaceObject.scales) {
+    interfaceObject.scales.forEach(function (scaleObj) {
         var position = Number(scaleObj.position) * 0.01;
         var pixelPosition = (position * $(this.canvas).width()) + offset;
         var scaleDOM = document.createElement('span');
         scaleDOM.className = "ape-marker-text";
         scaleDOM.textContent = scaleObj.text;
-        scaleDOM.setAttribute('value', position)
+        scaleDOM.setAttribute('value', position);
         this.scale.appendChild(scaleDOM);
         scaleDOM.style.left = Math.floor((pixelPosition - ($(scaleDOM).width() / 2))) + 'px';
-    }
+    }, this);
 
     this.createSliderObject = function (audioObject, label) {
         var trackObj = document.createElement('div');
@@ -548,7 +513,7 @@
         trackObj.className = 'track-slider track-slider-disabled track-slider-' + audioObject.id;
         trackObj.id = 'track-slider-' + this.id + '-' + audioObject.id;
         trackObj.setAttribute('trackIndex', audioObject.id);
-        if (this.name != undefined) {
+        if (this.name !== undefined) {
             trackObj.setAttribute('interface-name', this.name);
         } else {
             trackObj.setAttribute('interface-name', this.id);
@@ -572,21 +537,20 @@
     };
 
     this.resize = function (event) {
-        var width = window.innerWidth;
         var sliderDiv = this.canvas;
         var sliderScaleDiv = this.scale;
         var width = $(sliderDiv).width();
         var marginsize = 50;
         // Move sliders into new position
-        for (var index = 0; index < this.sliders.length; index++) {
-            var pix = Number(this.sliders[index].getAttribute("slider-value")) * width;
-            this.sliders[index].style.left = (pix + marginsize) + 'px';
-        }
+        this.sliders.forEach(function (slider, index) {
+            var pix = Number(slider.getAttribute("slider-value")) * width;
+            slider.style.left = (pix + marginsize) + 'px';
+        });
 
         // Move scale labels
         for (var index = 0; index < this.scale.children.length; index++) {
             var scaleObj = this.scale.children[index];
-            var position = Number(scaleObj.attributes['value'].value);
+            var position = Number(scaleObj.attributes.value.value);
             var pixelPosition = (position * width) + marginsize;
             scaleObj.style.left = Math.floor((pixelPosition - ($(scaleObj).width() / 2))) + 'px';
         }
@@ -671,7 +635,7 @@
         // audioObject has an error!!
         this.playback.textContent = "Error";
         $(this.playback).addClass("error-colour");
-    }
+    };
 }
 
 function outsideReferenceDOM(audioObject, index, inject) {
@@ -684,16 +648,13 @@
     this.outsideReferenceHolder.appendChild(outsideReferenceHolderspan);
     this.outsideReferenceHolder.setAttribute('track-id', index);
 
-    this.outsideReferenceHolder.onclick = function (event) {
-        audioEngineContext.play(event.currentTarget.getAttribute('track-id'));
+    this.handleEvent = function (event) {
+        audioEngineContext.play(audioObject.id);
         $('.track-slider').removeClass('track-slider-playing');
         $('.comment-div').removeClass('comment-box-playing');
-        if (event.currentTarget.nodeName == 'DIV') {
-            $(event.currentTarget).addClass('track-slider-playing');
-        } else {
-            $(event.currentTarget.parentElement).addClass('track-slider-playing');
-        }
+        $(this.outsideReferenceHolder).addClass('track-slider-playing');
     };
+    this.outsideReferenceHolder.addEventListener("click", this.handleEvent);
     inject.appendChild(this.outsideReferenceHolder);
     this.enable = function () {
         if (this.parent.state == 1) {
@@ -733,7 +694,7 @@
         // audioObject has an error!!
         this.outsideReferenceHolder.textContent = "Error";
         $(this.outsideReferenceHolder).addClass("error-colour");
-    }
+    };
 }
 
 function buttonSubmitClick() {
@@ -741,50 +702,36 @@
         canContinue = true;
 
     // Check that the anchor and reference objects are correctly placed
-    if (interfaceContext.checkHiddenAnchor() == false) {
+    if (interfaceContext.checkHiddenAnchor() === false) {
         return;
     }
-    if (interfaceContext.checkHiddenReference() == false) {
+    if (interfaceContext.checkHiddenReference() === false) {
         return;
     }
 
     for (var i = 0; i < checks.length; i++) {
         if (checks[i].type == 'check') {
+            var checkState = true;
             switch (checks[i].name) {
                 case 'fragmentPlayed':
                     // Check if all fragments have been played
-                    var checkState = interfaceContext.checkAllPlayed();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllPlayed();
                     break;
                 case 'fragmentFullPlayback':
                     // Check all fragments have been played to their full length
-                    var checkState = interfaceContext.checkFragmentsFullyPlayed();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkFragmentsFullyPlayed();
                     break;
                 case 'fragmentMoved':
                     // Check all fragment sliders have been moved.
-                    var checkState = interfaceContext.checkAllMoved();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllMoved();
                     break;
                 case 'fragmentComments':
                     // Check all fragment sliders have been moved.
-                    var checkState = interfaceContext.checkAllCommented();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllCommented();
                     break;
                 case 'scalerange':
                     // Check the scale is used to its full width outlined by the node
-                    var checkState = interfaceContext.checkScaleRange();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkScaleRange();
                     break;
                 default:
                     console.log("WARNING - Check option " + checks[i].name + " is not supported on this interface");
@@ -792,7 +739,8 @@
             }
 
         }
-        if (!canContinue) {
+        if (checkState === false) {
+            canContinue = false;
             break;
         }
     }
@@ -803,7 +751,7 @@
             playback.click();
             // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
         } else {
-            if (audioEngineContext.timer.testStarted == false) {
+            if (audioEngineContext.timer.testStarted === false) {
                 interfaceContext.lightbox.post("Warning", 'You have not started the test! Please click a fragment to begin the test!');
                 return;
             }
@@ -847,9 +795,9 @@
     var audioelements = store.getElementsByTagName("audioelement");
     for (var i = 0; i < audioelements.length; i++) {
         // Have to append the metric specific nodes
-        if (pageSpecification.outsideReference == null || pageSpecification.outsideReference.id != audioelements[i].id) {
+        if (pageSpecification.outsideReference === undefined || pageSpecification.outsideReference.id != audioelements[i].id) {
             var inject = audioelements[i].getElementsByTagName("metric");
-            if (inject.length == 0) {
+            if (inject.length === 0) {
                 inject = storage.document.createElement("metric");
             } else {
                 inject = inject[0];
@@ -861,7 +809,6 @@
                     if (name == "elementTracker" || name == "elementTrackerFull" || name == "elementInitialPosition" || name == "elementFlagMoved") {
                         mrnodes[j].setAttribute("interface-name", interfaceContext.interfaceSliders[k].name);
                         mrnodes[j].setAttribute("interface-id", k);
-                        inject.appendChild(mrnodes[j]);
                     }
                 }
             }
--- a/interfaces/discrete.js	Thu Apr 13 11:36:24 2017 +0100
+++ b/interfaces/discrete.js	Fri Apr 14 16:02:52 2017 +0100
@@ -1,3 +1,4 @@
+/* globals interfaceContext, document, window, $, specification, audioEngineContext, console, window, testState, storage */
 // Once this is loaded and parsed, begin execution
 loadInterface();
 
@@ -19,7 +20,7 @@
     titleSpan.id = "test-title";
 
     // Set title to that defined in XML, else set to default
-    if (titleAttr != undefined) {
+    if (titleAttr !== undefined) {
         titleSpan.textContent = titleAttr;
     } else {
         titleSpan.textContent = 'Listening test';
@@ -30,7 +31,8 @@
     var pagetitle = document.createElement('div');
     pagetitle.className = "pageTitle";
     pagetitle.align = "center";
-    var titleSpan = document.createElement('span');
+
+    titleSpan = document.createElement('span');
     titleSpan.id = "pageTitle";
     pagetitle.appendChild(titleSpan);
 
@@ -112,7 +114,7 @@
     // Load the full interface
     testState.initialise();
     testState.advanceState();
-};
+}
 
 function loadTest(page) {
     // Called each time a new test page is to be build. The page specification node is the only item passed in
@@ -128,10 +130,10 @@
 
     // Set the page title
     if (typeof page.title == "string" && page.title.length > 0) {
-        document.getElementById("test-title").textContent = page.title
+        document.getElementById("test-title").textContent = page.title;
     }
 
-    if (interfaceObj.title != null) {
+    if (interfaceObj.title !== null) {
         document.getElementById("pageTitle").textContent = interfaceObj.title;
     }
 
@@ -142,17 +144,17 @@
     sliderBox.innerHTML = "";
 
     var commentBoxPrefix = "Comment on track";
-    if (interfaceObj.commentBoxPrefix != undefined) {
+    if (interfaceObj.commentBoxPrefix !== undefined) {
         commentBoxPrefix = interfaceObj.commentBoxPrefix;
     }
     var loopPlayback = page.loop;
 
-    for (var option of interfaceObj.options) {
+    interfaceObj.options.forEach(function (option) {
         if (option.type == "show") {
             switch (option.name) {
                 case "playhead":
                     var playbackHolder = document.getElementById('playback-holder');
-                    if (playbackHolder == null) {
+                    if (playbackHolder === null) {
                         playbackHolder = document.createElement('div');
                         playbackHolder.style.width = "100%";
                         playbackHolder.align = 'center';
@@ -162,7 +164,7 @@
                     break;
                 case "page-count":
                     var pagecountHolder = document.getElementById('page-count');
-                    if (pagecountHolder == null) {
+                    if (pagecountHolder === null) {
                         pagecountHolder = document.createElement('div');
                         pagecountHolder.id = 'page-count';
                     }
@@ -171,7 +173,7 @@
                     inject.appendChild(pagecountHolder);
                     break;
                 case "volume":
-                    if (document.getElementById('master-volume-holder') == null) {
+                    if (document.getElementById('master-volume-holder') === null) {
                         feedbackHolder.appendChild(interfaceContext.volume.object);
                     }
                     break;
@@ -180,7 +182,7 @@
                     break;
             }
         }
-    }
+    });
 
     // Find all the audioElements from the audioHolder
     var index = 0;
@@ -223,9 +225,9 @@
     // An example node, you can make this however you want for each audioElement.
     // However, every audioObject (audioEngineContext.audioObject) MUST have an interface object with the following
     // You attach them by calling audioObject.bindInterface( )
-    if (interfaceScales == null || interfaceScales.length == 0) {
+    if (interfaceScales === null || interfaceScales.length === 0) {
         console.log("WARNING: The discrete radio's are built depending on the number of scale points specified! Ensure you have some specified. Defaulting to 5 for now!");
-        numOptions = 5;
+        var numOptions = 5;
     }
     this.parent = audioObject;
 
@@ -246,6 +248,23 @@
 
     this.discreteHolder.className = "track-slider-range";
     this.discreteHolder.style.width = window.innerWidth - 500 + 'px';
+    this.radioClicked = function (event) {
+        var time = audioEngineContext.timer.getTestTime();
+        if (audioEngineContext.status === 0) {
+            event.currentTarget.checked = false;
+            return;
+        }
+        var id = this.parent.id;
+        var position = this.getValue();
+        this.parent.metric.moved(time, position);
+        console.log('slider ' + id + ' moved to ' + position + ' (' + time + ')');
+
+    };
+    this.handleEvent = function (event) {
+        if (event.currentTarget.getAttribute("name") === this.parent.specification.id) {
+            this.radioClicked(event);
+        }
+    };
     for (var i = 0; i < interfaceScales.length; i++) {
         var node = document.createElement('input');
         node.setAttribute('type', 'radio');
@@ -256,17 +275,7 @@
         node.setAttribute('id', audioObject.specification.id + '-' + String(i));
         this.discretes.push(node);
         this.discreteHolder.appendChild(node);
-        node.onclick = function (event) {
-            if (audioEngineContext.status == 0) {
-                event.currentTarget.checked = false;
-                return;
-            }
-            var time = audioEngineContext.timer.getTestTime();
-            var id = Number(event.currentTarget.parentNode.parentNode.getAttribute('trackIndex'));
-            var value = event.currentTarget.getAttribute('position') / 100.0;
-            audioEngineContext.audioObjects[id].metric.moved(time, value);
-            console.log('slider ' + id + ' moved to ' + value + ' (' + time + ')');
-        };
+        node.addEventListener("click", this);
     }
 
     this.play.className = 'track-slider-button';
@@ -300,9 +309,9 @@
         this.play.disabled = false;
         this.play.textContent = "Play";
         $(this.slider).removeClass('track-slider-disabled');
-        for (var radio of this.discretes) {
-            radio.disabled = false;
-        }
+        this.discretes.forEach(function (elem) {
+            elem.disabled = false;
+        });
     };
     this.updateLoading = function (progress) {
         // progress is a value from 0 to 100 indicating the current download state of media files
@@ -322,14 +331,14 @@
         $(this.holder).addClass('track-slider-playing');
         var outsideReference = document.getElementById('outside-reference');
         this.play.textContent = "Listening";
-        if (outsideReference != null) {
+        if (outsideReference !== null) {
             $(outsideReference).removeClass('track-slider-playing');
         }
         if (this.parent.specification.parent.playOne || specification.playOne) {
             $('.track-slider-button').text = "Wait";
             $('.track-slider-button').attr("disabled", "true");
         }
-    }
+    };
     this.stopPlayback = function () {
         // Called by audioObject when playback stops
         if (this.play.getAttribute("playstate") == "playing") {
@@ -339,18 +348,17 @@
             this.play.textContent = "Play";
             $('.track-slider-button').removeAttr("disabled");
         }
-    }
+    };
 
     this.getValue = function () {
         // Return the current value of the object. If there is no value, return -1
-        var value = -1;
-        for (var i = 0; i < this.discretes.length; i++) {
-            if (this.discretes[i].checked == true) {
-                value = this.discretes[i].getAttribute('position') / 100.0;
-                break;
-            }
+        var checkedElement = this.discretes.find(function (elem) {
+            return elem.checked;
+        });
+        if (checkedElement === undefined) {
+            return -1;
         }
-        return value;
+        return checkedElement.getAttribute("position") / 100.0;
     };
     this.getPresentedId = function () {
         // Return the presented ID of the object. For instance, the APE has sliders starting from 0. Whilst AB has alphabetical scale
@@ -374,8 +382,8 @@
         // audioObject has an error!!
         this.playback.textContent = "Error";
         $(this.playback).addClass("error-colour");
-    }
-};
+    };
+}
 
 function resizeWindow(event) {
     // Called on every window resize event, use this to scale your page properly
@@ -415,7 +423,7 @@
     textHolder.innerHTML = "";
     ctx.fillStyle = "#000000";
     ctx.setLineDash([1, 4]);
-    for (var scale of scales) {
+    scales.forEach(function (scale) {
         var posPercent = scale.position / 100.0;
         var posPix = Math.round(width * posPercent);
         if (posPix <= 0) {
@@ -437,7 +445,7 @@
         textHolder.appendChild(text);
         text.style.width = $(text.children[0]).width() + 'px';
         text.style.left = (posPix + 150 - ($(text).width() / 2)) + 'px';
-    }
+    });
 }
 
 function buttonSubmitClick() // TODO: Only when all songs have been played!
@@ -446,57 +454,45 @@
         canContinue = true;
 
     // Check that the anchor and reference objects are correctly placed
-    if (interfaceContext.checkHiddenAnchor() == false) {
+    if (interfaceContext.checkHiddenAnchor() === false) {
         return;
     }
-    if (interfaceContext.checkHiddenReference() == false) {
+    if (interfaceContext.checkHiddenReference() === false) {
         return;
     }
 
     for (var i = 0; i < checks.length; i++) {
+        var checkState;
         if (checks[i].type == 'check') {
             switch (checks[i].name) {
                 case 'fragmentPlayed':
                     // Check if all fragments have been played
-                    var checkState = interfaceContext.checkAllPlayed();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllPlayed();
                     break;
                 case 'fragmentFullPlayback':
                     // Check all fragments have been played to their full length
-                    var checkState = interfaceContext.checkAllPlayed();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllPlayed();
                     console.log('NOTE: fragmentFullPlayback not currently implemented, performing check fragmentPlayed instead');
                     break;
                 case 'fragmentMoved':
                     // Check all fragment sliders have been moved.
-                    var checkState = interfaceContext.checkAllMoved();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllMoved();
                     break;
                 case 'fragmentComments':
                     // Check all fragment sliders have been moved.
-                    var checkState = interfaceContext.checkAllCommented();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllCommented();
                     break;
                 case 'scalerange':
                     // Check the scale has been used effectively
-                    var checkState = interfaceContext.checkScaleRange(checks[i].min, checks[i].max);
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkScaleRange(checks[i].min, checks[i].max);
                     break;
                 default:
                     console.log("WARNING - Check option " + checks[i].check + " is not supported on this interface");
                     break;
             }
-
+            if (checkState === false) {
+                canContinue = false;
+            }
         }
         if (!canContinue) {
             break;
@@ -509,7 +505,7 @@
             playback.click();
             // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
         } else {
-            if (audioEngineContext.timer.testStarted == false) {
+            if (audioEngineContext.timer.testStarted === false) {
                 interfaceContext.lightbox.post("Warning", 'You have not started the test! Please press start to begin the test!');
                 return;
             }
--- a/interfaces/horizontal-sliders.js	Thu Apr 13 11:36:24 2017 +0100
+++ b/interfaces/horizontal-sliders.js	Fri Apr 14 16:02:52 2017 +0100
@@ -1,4 +1,5 @@
 // Once this is loaded and parsed, begin execution
+/*globals interfaceContext, window, document, specification, audioEngineContext, console, testState, $, storage */
 loadInterface();
 
 function loadInterface() {
@@ -19,7 +20,7 @@
     titleSpan.id = "test-title";
 
     // Set title to that defined in XML, else set to default
-    if (titleAttr != undefined) {
+    if (titleAttr !== undefined) {
         titleSpan.textContent = titleAttr;
     } else {
         titleSpan.textContent = 'Listening test';
@@ -30,7 +31,8 @@
     var pagetitle = document.createElement('div');
     pagetitle.className = "pageTitle";
     pagetitle.align = "center";
-    var titleSpan = document.createElement('span');
+
+    titleSpan = document.createElement('span');
     titleSpan.id = "pageTitle";
     pagetitle.appendChild(titleSpan);
 
@@ -111,7 +113,7 @@
     // Load the full interface
     testState.initialise();
     testState.advanceState();
-};
+}
 
 function loadTest(page) {
     // Called each time a new test page is to be build. The page specification node is the only item passed in
@@ -128,10 +130,10 @@
 
     // Set the page title
     if (typeof page.title == "string" && page.title.length > 0) {
-        document.getElementById("test-title").textContent = page.title
+        document.getElementById("test-title").textContent = page.title;
     }
 
-    if (interfaceObj.title != null) {
+    if (interfaceObj.title !== null) {
         document.getElementById("pageTitle").textContent = interfaceObj.title;
     }
 
@@ -142,7 +144,7 @@
     sliderBox.innerHTML = "";
 
     var commentBoxPrefix = "Comment on track";
-    if (interfaceObj.commentBoxPrefix != undefined) {
+    if (interfaceObj.commentBoxPrefix !== undefined) {
         commentBoxPrefix = interfaceObj.commentBoxPrefix;
     }
     var loopPlayback = page.loop;
@@ -186,12 +188,12 @@
         }
 
     });
-    for (var option of interfaceObj.options) {
+    interfaceObj.options.forEach(function (option) {
         if (option.type == "show") {
             switch (option.name) {
                 case "playhead":
                     var playbackHolder = document.getElementById('playback-holder');
-                    if (playbackHolder == null) {
+                    if (playbackHolder === null) {
                         playbackHolder = document.createElement('div');
                         playbackHolder.style.width = "100%";
                         playbackHolder.align = 'center';
@@ -201,7 +203,7 @@
                     break;
                 case "page-count":
                     var pagecountHolder = document.getElementById('page-count');
-                    if (pagecountHolder == null) {
+                    if (pagecountHolder === null) {
                         pagecountHolder = document.createElement('div');
                         pagecountHolder.id = 'page-count';
                     }
@@ -210,7 +212,7 @@
                     inject.appendChild(pagecountHolder);
                     break;
                 case "volume":
-                    if (document.getElementById('master-volume-holder') == null) {
+                    if (document.getElementById('master-volume-holder') === null) {
                         feedbackHolder.appendChild(interfaceContext.volume.object);
                     }
                     break;
@@ -219,7 +221,7 @@
                     break;
             }
         }
-    }
+    });
     // Auto-align
     resizeWindow(null);
 }
@@ -291,7 +293,7 @@
         $(".track-slider").removeClass('track-slider-playing');
         $(this.holder).addClass('track-slider-playing');
         var outsideReference = document.getElementById('outside-reference');
-        if (outsideReference != null) {
+        if (outsideReference !== null) {
             $(outsideReference).removeClass('track-slider-playing');
         }
     };
@@ -326,8 +328,8 @@
         // audioObject has an error!!
         this.playback.textContent = "Error";
         $(this.playback).addClass("error-colour");
-    }
-};
+    };
+}
 
 function resizeWindow(event) {
     // Called on every window resize event, use this to scale your page properly
@@ -367,7 +369,7 @@
     textHolder.innerHTML = "";
     ctx.fillStyle = "#000000";
     ctx.setLineDash([1, 4]);
-    for (var scale of scales) {
+    scales.forEach(function (scale) {
         var posPercent = scale.position / 100.0;
         var posPix = Math.round(width * posPercent);
         if (posPix <= 0) {
@@ -389,7 +391,7 @@
         textHolder.appendChild(text);
         text.style.width = Math.ceil($(text).width()) + 'px';
         text.style.left = (posPix + 100 - ($(text).width() / 2)) + 'px';
-    }
+    });
 }
 
 function buttonSubmitClick() // TODO: Only when all songs have been played!
@@ -398,59 +400,46 @@
         canContinue = true;
 
     // Check that the anchor and reference objects are correctly placed
-    if (interfaceContext.checkHiddenAnchor() == false) {
+    if (interfaceContext.checkHiddenAnchor() === false) {
         return;
     }
-    if (interfaceContext.checkHiddenReference() == false) {
+    if (interfaceContext.checkHiddenReference() === false) {
         return;
     }
 
     for (var i = 0; i < checks.length; i++) {
+        var checkState = true;
         if (checks[i].type == 'check') {
             switch (checks[i].name) {
                 case 'fragmentPlayed':
                     // Check if all fragments have been played
-                    var checkState = interfaceContext.checkAllPlayed();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllPlayed();
                     break;
                 case 'fragmentFullPlayback':
                     // Check all fragments have been played to their full length
-                    var checkState = interfaceContext.checkAllPlayed();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllPlayed();
                     console.log('NOTE: fragmentFullPlayback not currently implemented, performing check fragmentPlayed instead');
                     break;
                 case 'fragmentMoved':
                     // Check all fragment sliders have been moved.
-                    var checkState = interfaceContext.checkAllMoved();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllMoved();
                     break;
                 case 'fragmentComments':
                     // Check all fragment sliders have been moved.
-                    var checkState = interfaceContext.checkAllCommented();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllCommented();
                     break;
                 case 'scalerange':
                     // Check the scale has been used effectively
-                    var checkState = interfaceContext.checkScaleRange(checks[i].min, checks[i].max);
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkScaleRange(checks[i].min, checks[i].max);
+
                     break;
                 default:
                     console.log("WARNING - Check option " + checks[i].check + " is not supported on this interface");
                     break;
             }
-
         }
-        if (!canContinue) {
+        if (checkState === false) {
+            canContinue = false;
             break;
         }
     }
@@ -461,7 +450,7 @@
             playback.click();
             // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
         } else {
-            if (audioEngineContext.timer.testStarted == false) {
+            if (audioEngineContext.timer.testStarted === false) {
                 interfaceContext.lightbox.post("Warning", 'You have not started the test! Please press start to begin the test!');
                 return;
             }
--- a/interfaces/mushra.js	Thu Apr 13 11:36:24 2017 +0100
+++ b/interfaces/mushra.js	Fri Apr 14 16:02:52 2017 +0100
@@ -2,7 +2,7 @@
  *  mushra.js
  *  Create the MUSHRA interface
  */
-
+/*globals window, interfaceContext, document, $, specification, audioEngineContext, console, testState, storage */
 // Once this is loaded and parsed, begin execution
 loadInterface();
 
@@ -25,7 +25,7 @@
     titleSpan.id = "test-title";
 
     // Set title to that defined in XML, else set to default
-    if (titleAttr != undefined) {
+    if (titleAttr !== undefined) {
         titleSpan.textContent = titleAttr;
     } else {
         titleSpan.textContent = 'Listening test';
@@ -36,7 +36,8 @@
     var pagetitle = document.createElement('div');
     pagetitle.className = "pageTitle";
     pagetitle.align = "center";
-    var titleSpan = document.createElement('span');
+
+    titleSpan = document.createElement('span');
     titleSpan.id = "pageTitle";
     pagetitle.appendChild(titleSpan);
 
@@ -129,10 +130,10 @@
 
     // Set the page title
     if (typeof audioHolderObject.title == "string" && audioHolderObject.title.length > 0) {
-        document.getElementById("test-title").textContent = audioHolderObject.title
+        document.getElementById("test-title").textContent = audioHolderObject.title;
     }
 
-    if (interfaceObj.title != null) {
+    if (interfaceObj.title !== null) {
         document.getElementById("pageTitle").textContent = interfaceObj.title;
     }
 
@@ -144,12 +145,12 @@
     sliderBox.innerHTML = "";
 
     var commentBoxPrefix = "Comment on track";
-    if (interfaceObj.commentBoxPrefix != undefined) {
+    if (interfaceObj.commentBoxPrefix !== undefined) {
         commentBoxPrefix = interfaceObj.commentBoxPrefix;
     }
     var loopPlayback = audioHolderObject.loop;
 
-    currentTestHolder = document.createElement('audioHolder');
+    var currentTestHolder = document.createElement('audioHolder');
     currentTestHolder.id = audioHolderObject.id;
     currentTestHolder.repeatCount = audioHolderObject.repeatCount;
 
@@ -192,18 +193,18 @@
     if (testState.currentStateMap.restrictMovement) {
         $(".track-slider-range").addClass("track-slider-range-disabled");
         $(".track-slider-range").each(function (i, e) {
-            e.disabled = true
+            e.disabled = true;
         });
     }
 
 
     var interfaceOptions = interfaceObj.options;
-    for (var option of interfaceOptions) {
+    interfaceOptions.forEach(function (option) {
         if (option.type == "show") {
             switch (option.name) {
                 case "playhead":
                     var playbackHolder = document.getElementById('playback-holder');
-                    if (playbackHolder == null) {
+                    if (playbackHolder === null) {
                         playbackHolder = document.createElement('div');
                         playbackHolder.style.width = "100%";
                         playbackHolder.align = 'center';
@@ -213,7 +214,7 @@
                     break;
                 case "page-count":
                     var pagecountHolder = document.getElementById('page-count');
-                    if (pagecountHolder == null) {
+                    if (pagecountHolder === null) {
                         pagecountHolder = document.createElement('div');
                         pagecountHolder.id = 'page-count';
                     }
@@ -222,7 +223,7 @@
                     inject.appendChild(pagecountHolder);
                     break;
                 case "volume":
-                    if (document.getElementById('master-volume-holder') == null) {
+                    if (document.getElementById('master-volume-holder') === null) {
                         feedbackHolder.appendChild(interfaceContext.volume.object);
                     }
                     break;
@@ -231,7 +232,7 @@
                     break;
             }
         }
-    }
+    });
 
     $(audioHolderObject.commentQuestions).each(function (index, element) {
         var node = interfaceContext.createCommentQuestion(element);
@@ -257,7 +258,7 @@
     this.holder.appendChild(this.slider);
     this.holder.appendChild(this.play);
     this.holder.align = "center";
-    if (label == 0) {
+    if (label === 0) {
         this.holder.style.marginLeft = '0px';
     }
     this.holder.setAttribute('trackIndex', audioObject.id);
@@ -316,7 +317,7 @@
         $(".track-slider").removeClass('track-slider-playing');
         $(this.holder).addClass('track-slider-playing');
         var outsideReference = document.getElementById('outside-reference');
-        if (outsideReference != null) {
+        if (outsideReference !== null) {
             $(outsideReference).removeClass('track-slider-playing');
         }
         this.play.textContent = "Stop";
@@ -374,7 +375,7 @@
         // audioObject has an error!!
         this.playback.textContent = "Error";
         $(this.playback).addClass("error-colour");
-    }
+    };
 }
 
 function resizeWindow(event) {
@@ -382,7 +383,7 @@
     // MANDATORY FUNCTION
 
     var outsideRef = document.getElementById('outside-reference');
-    if (outsideRef != null) {
+    if (outsideRef !== null) {
         outsideRef.style.left = (window.innerWidth - 120) / 2 + 'px';
     }
 
@@ -428,7 +429,7 @@
     var textHolder = document.getElementById('scale-text-holder');
     textHolder.innerHTML = "";
     var lastHeight = 0;
-    for (var scale of scales) {
+    scales.forEach(function (scale) {
         var posPercent = scale.position / 100.0;
         var posPix = (1 - posPercent) * (draw_heights[1] - draw_heights[0]) + draw_heights[0];
         ctx.fillStyle = "#000000";
@@ -446,7 +447,7 @@
         text.style.top = (posPix - 9) + 'px';
         text.style.left = 100 - ($(text).width() + 3) + 'px';
         lastHeight = posPix;
-    }
+    });
 }
 
 function buttonSubmitClick() // TODO: Only when all songs have been played!
@@ -455,51 +456,37 @@
         canContinue = true;
 
     // Check that the anchor and reference objects are correctly placed
-    if (interfaceContext.checkHiddenAnchor() == false) {
+    if (interfaceContext.checkHiddenAnchor() === false) {
         return;
     }
-    if (interfaceContext.checkHiddenReference() == false) {
+    if (interfaceContext.checkHiddenReference() === false) {
         return;
     }
 
     for (var i = 0; i < checks.length; i++) {
+        var checkState = true;
         if (checks[i].type == 'check') {
             switch (checks[i].name) {
                 case 'fragmentPlayed':
                     // Check if all fragments have been played
-                    var checkState = interfaceContext.checkAllPlayed();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllPlayed();
                     break;
                 case 'fragmentFullPlayback':
                     // Check all fragments have been played to their full length
-                    var checkState = interfaceContext.checkAllPlayed();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllPlayed();
                     console.log('NOTE: fragmentFullPlayback not currently implemented, performing check fragmentPlayed instead');
                     break;
                 case 'fragmentMoved':
                     // Check all fragment sliders have been moved.
-                    var checkState = interfaceContext.checkAllMoved();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllMoved();
                     break;
                 case 'fragmentComments':
                     // Check all fragment sliders have been moved.
-                    var checkState = interfaceContext.checkAllCommented();
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkAllCommented();
                     break;
                 case 'scalerange':
                     // Check the scale has been used effectively
-                    var checkState = interfaceContext.checkScaleRange(checks[i].min, checks[i].max);
-                    if (checkState == false) {
-                        canContinue = false;
-                    }
+                    checkState = interfaceContext.checkScaleRange(checks[i].min, checks[i].max);
                     break;
                 default:
                     console.log("WARNING - Check option " + checks[i].check + " is not supported on this interface");
@@ -507,7 +494,8 @@
             }
 
         }
-        if (!canContinue) {
+        if (checkState === false) {
+            canContinue = false;
             break;
         }
     }
@@ -518,7 +506,7 @@
             playback.click();
             // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
         } else {
-            if (audioEngineContext.timer.testStarted == false) {
+            if (audioEngineContext.timer.testStarted === false) {
                 interfaceContext.lightbox.post("Message", 'You have not started the test! Please press start to begin the test!');
                 return;
             }
--- a/interfaces/timeline.js	Thu Apr 13 11:36:24 2017 +0100
+++ b/interfaces/timeline.js	Fri Apr 14 16:02:52 2017 +0100
@@ -2,7 +2,7 @@
  * WAET Timeline
  * This interface plots a waveform timeline per audio fragment on a page. Clicking on the fragment will generate a comment box for processing.
  */
-
+/*globals interfaceContext, window, document, console, audioEngineContext, testState, $, storage */
 // Once this is loaded and parsed, begin execution
 loadInterface();
 
@@ -77,7 +77,7 @@
     // Load the full interface
     testState.initialise();
     testState.advanceState();
-};
+}
 
 function loadTest(page) {
     // Called each time a new test page is to be build. The page specification node is the only item passed in
@@ -94,7 +94,7 @@
         document.getElementById("test-title").textContent = page.title;
     }
 
-    if (interfaceObj.title != null) {
+    if (interfaceObj.title !== null) {
         document.getElementById("page-title").textContent = interfaceObj.title;
     }
 
@@ -103,7 +103,7 @@
     outsideReferenceHolder.innerHTML = "";
 
     var commentBoxPrefix = "Comment on track";
-    if (interfaceObj.commentBoxPrefix != undefined) {
+    if (interfaceObj.commentBoxPrefix !== undefined) {
         commentBoxPrefix = interfaceObj.commentBoxPrefix;
     }
     var index = 0;
@@ -116,7 +116,7 @@
         var audioObject = audioEngineContext.newTrack(element);
         if (page.audioElements.type == 'outside-reference') {
             var refNode = interfaceContext.outsideReferenceDOM(audioObject, index, outsideReferenceHolder);
-            audioObject.bindInterface(orNode);
+            audioObject.bindInterface(refNode);
         } else {
             var label = interfaceContext.getLabel(labelType, index, page.labelStart);
             var node = new interfaceObject(audioObject, label);
@@ -169,7 +169,7 @@
             var titleHolder = document.createElement("div");
             titleHolder.className = "comment-entry-header";
             this.title = document.createElement("span");
-            if (str != undefined) {
+            if (str !== undefined) {
                 this.title.textContent = str;
             } else {
                 this.title.textContent = "Time: " + time.toFixed(2) + "s";
@@ -186,7 +186,7 @@
                 handleEvent: function () {
                     this.parent.parent.deleteComment(this.parent);
                 }
-            }
+            };
             this.clear.DOM.textContent = "Delete";
             this.clear.DOM.addEventListener("click", this.clear);
             titleHolder.appendChild(this.clear.DOM);
@@ -199,7 +199,7 @@
                 elem_w = Math.max(elem_w, 190);
                 this.DOM.style.width = elem_w + "px";
                 this.textarea.style.width = (elem_w - 5) + "px";
-            }
+            };
             this.buildXML = function (root) {
                 //storage.document.createElement();
                 var node = storage.document.createElement("comment");
@@ -211,7 +211,7 @@
                 node.appendChild(question);
                 node.appendChild(comment);
                 root.appendChild(node);
-            }
+            };
             this.resize();
         },
         newComment: function (time) {
@@ -240,7 +240,7 @@
                 this.deleteComment(this.list[0]);
             }
         }
-    }
+    };
 
     this.canvas = {
         parent: this,
@@ -283,7 +283,7 @@
             }
         },
         drawWaveform: function () {
-            if (this.parent.parent == undefined || this.parent.parent.buffer == undefined) {
+            if (this.parent.parent === undefined || this.parent.parent.buffer === undefined) {
                 return;
             }
             var buffer = this.parent.parent.buffer.buffer;
@@ -342,7 +342,7 @@
             context.stroke();
         },
         drawMarkers: function () {
-            if (this.parent.parent == undefined || this.parent.parent.buffer == undefined) {
+            if (this.parent.parent === undefined || this.parent.parent.buffer === undefined) {
                 return;
             }
             var context = this.layer3.getContext("2d");
@@ -362,7 +362,7 @@
             var context = canvas.getContext("2d");
             context.clearRect(0, 0, canvas.width, canvas.height);
         }
-    }
+    };
     this.canvas.layer1.className = "timeline-element-canvas canvas-layer1 canvas-disabled";
     this.canvas.layer2.className = "timeline-element-canvas canvas-layer2";
     this.canvas.layer3.className = "timeline-element-canvas canvas-layer3";
@@ -393,7 +393,7 @@
                 audioEngineContext.stop();
             }
         }
-    }
+    };
     this.playButton.DOM.addEventListener("click", this.playButton);
     this.playButton.DOM.className = "timeline-button timeline-button-disabled";
     this.playButton.DOM.disabled = true;
@@ -408,7 +408,7 @@
         root.style.width = w + "px";
         var c_w = w - 100;
         this.canvas.resize(c_w);
-    }
+    };
 
     this.enable = function () {
         // This is used to tell the interface object that playback of this node is ready
@@ -459,8 +459,8 @@
     };
     this.error = function () {
         // If there is an error with the audioObject, this will be called to indicate a failure
-    }
-};
+    };
+}
 
 function resizeWindow(event) {
     // Called on every window resize event, use this to scale your page properly
@@ -470,7 +470,7 @@
 }
 
 function buttonSubmitClick() {
-    if (audioEngineContext.timer.testStarted == false) {
+    if (audioEngineContext.timer.testStarted === false) {
         interfaceContext.lightbox.post("Warning", 'You have not started the test! Please click play on a sample to begin the test!');
         return;
     }
@@ -495,7 +495,7 @@
                     console.log("WARNING - Check option " + checks[i].check + " is not supported on this interface");
                     break;
             }
-            if (checkState == false) {
+            if (checkState === false) {
                 canContinue = false;
             }
         }
--- a/js/WAVE.js	Thu Apr 13 11:36:24 2017 +0100
+++ b/js/WAVE.js	Fri Apr 14 16:02:52 2017 +0100
@@ -1,6 +1,7 @@
 // Decode and perform WAVE file byte level manipulation
+/*globals console, Uint8Array, Float32Array, Float64Array */
 
-find_subarray = function (arr, subarr) {
+function find_subarray(arr, subarr) {
     var arr_length = arr.length;
     var subarr_length = subarr.length;
     var last_check_index = arr_length - subarr_length;
@@ -15,7 +16,7 @@
             return i;
         }
     return -1;
-};
+}
 
 function convertToInteger(arr) {
     var value = 0;
@@ -35,24 +36,24 @@
 
 function WAVE() {
     // The WAVE file object
-    this.status == 'WAVE_DECLARED'
+    this.status = 'WAVE_DECLARED';
 
     this.decoded_data = null;
 
     this.RIFF = String(); //ChunkID
-    this.size; //ChunkSize
-    this.FT_Header; //Format
-    this.fmt_marker; //Subchunk1ID
-    this.formatDataLength; //Subchunk1Size
-    this.type; //AudioFormat
-    this.num_channels; //NumChannels
-    this.sample_rate; //SampleRate
-    this.byte_rate; //ByteRate
-    this.block_align; //BlockAlign
-    this.bits_per_sample; //BitsPerSample
-    this.data_header; //Subchunk2ID
-    this.data_size; //Subchunk2Size
-    this.num_samples;
+    this.size = undefined; //ChunkSize
+    this.FT_Header = undefined; //Format
+    this.fmt_marker = undefined; //Subchunk1ID
+    this.formatDataLength = undefined; //Subchunk1Size
+    this.type = undefined; //AudioFormat
+    this.num_channels = undefined; //NumChannels
+    this.sample_rate = undefined; //SampleRate
+    this.byte_rate = undefined; //ByteRate
+    this.block_align = undefined; //BlockAlign
+    this.bits_per_sample = undefined; //BitsPerSample
+    this.data_header = undefined; //Subchunk2ID
+    this.data_size = undefined; //Subchunk2Size
+    this.num_samples = undefined;
 
     this.open = function (IOArrayBuffer) {
         var IOView8 = new Uint8Array(IOArrayBuffer);
--- a/js/loader.js	Thu Apr 13 11:36:24 2017 +0100
+++ b/js/loader.js	Fri Apr 14 16:02:52 2017 +0100
@@ -1,8 +1,8 @@
 // Script to load the relevant JS files if the system supports it
-
+/*globals window, document */
 window.onload = function () {
     // First check if the Web Audio API is supported
-    if (window.AudioContext == undefined && window.webkitAudioContext == undefined) {
+    if (window.AudioContext === undefined && window.webkitAudioContext === undefined) {
         // Display unsuported error message
         var body = document.getElementsByTagName("body")[0];
         body.innerHTML = "<h1>Sorry! Your browser is not supported :(</h1><p>Your browser does not support the HTML5 Web Audio API. Please use one of the following supported browsers instead.<p>";
@@ -26,4 +26,4 @@
             head.appendChild(script);
         }
     }
-}
+};
--- a/js/loudness.js	Thu Apr 13 11:36:24 2017 +0100
+++ b/js/loudness.js	Fri Apr 14 16:02:52 2017 +0100
@@ -5,7 +5,7 @@
  * 	return gain values to correct for a target loudness or match loudness between
  *  multiple objects
  */
-
+/* globals webkitOfflineAudioContext, navigator, audioContext, Float32Array */
 var interval_cal_loudness_event = null;
 
 if (typeof OfflineAudioContext == "undefined") {
@@ -21,16 +21,16 @@
     if (navigator.platform == 'iPad' || navigator.platform == 'iPhone') {
         buffer.ready();
     }
-    if (buffer == undefined) {
+    if (buffer === undefined) {
         return 0;
     }
-    if (timescale == undefined) {
+    if (timescale === undefined) {
         timescale = "I";
     }
-    if (target == undefined) {
+    if (target === undefined) {
         target = -23;
     }
-    if (offlineContext == undefined) {
+    if (offlineContext === undefined) {
         offlineContext = new OfflineAudioContext(audioContext.destination.channelCount, buffer.buffer.duration * audioContext.sampleRate, audioContext.sampleRate);
     }
     // Create the required filters
@@ -77,12 +77,12 @@
 }
 
 function calculateMeanSquared(buffer, frame_dur, frame_overlap) {
-    frame_size = Math.floor(buffer.sampleRate * frame_dur);
-    step_size = Math.floor(frame_size * (1.0 - frame_overlap));
-    num_frames = Math.floor((buffer.length - frame_size) / step_size);
-    num_frames = Math.max(num_frames, 1);
+    var frame_size = Math.floor(buffer.sampleRate * frame_dur);
+    var step_size = Math.floor(frame_size * (1.0 - frame_overlap));
+    var num_frames = Math.floor((buffer.length - frame_size) / step_size);
+    num_frames = Math.max(num_frames, 0);
 
-    MS = Array(buffer.numberOfChannels);
+    var MS = Array(buffer.numberOfChannels);
     for (var c = 0; c < buffer.numberOfChannels; c++) {
         MS[c] = new Float32Array(num_frames);
         var data = buffer.getChannelData(c);
@@ -124,13 +124,14 @@
     var num_frames = source[0].length;
     var num_channels = source.length;
     var LK = Array(num_channels);
-    for (var c = 0; c < num_channels; c++) {
+    var n, c;
+    for (c = 0; c < num_channels; c++) {
         LK[c] = [];
     }
 
-    for (var n = 0; n < num_frames; n++) {
+    for (n = 0; n < num_frames; n++) {
         if (blocks[n] > threshold) {
-            for (var c = 0; c < num_channels; c++) {
+            for (c = 0; c < num_channels; c++) {
                 LK[c].push(source[c][n]);
             }
         }
--- a/js/specification.js	Thu Apr 13 11:36:24 2017 +0100
+++ b/js/specification.js	Fri Apr 14 16:02:52 2017 +0100
@@ -1,38 +1,40 @@
+/* globals document, console, DOMParser */
 function Specification() {
+    var schemaRoot;
+    var schemaString;
     // Handles the decoding of the project specification XML into a simple JavaScript Object.
 
     // <setup> attributes
-    this.interface = null;
-    this.projectReturn = null;
-    this.returnURL = null;
-    this.randomiseOrder = null;
-    this.poolSize = null;
-    this.loudness = null;
-    this.sampleRate = null;
-    this.calibration = null;
-    this.crossFade = null;
-    this.preSilence = null;
-    this.postSilence = null;
-    this.playOne = null;
+    this.interface = undefined;
+    this.projectReturn = undefined;
+    this.returnURL = undefined;
+    this.randomiseOrder = undefined;
+    this.poolSize = undefined;
+    this.loudness = undefined;
+    this.sampleRate = undefined;
+    this.calibration = undefined;
+    this.crossFade = undefined;
+    this.preSilence = undefined;
+    this.postSilence = undefined;
+    this.playOne = undefined;
 
     // nodes
-    this.metrics = null;
+    this.metrics = undefined;
     this.preTest = undefined;
     this.postTest = undefined;
     this.pages = [];
-    this.interfaces = null;
+    this.interfaces = undefined;
     this.errors = [];
-    this.schema = null;
     this.exitText = "Thank you.";
 
-    this.processAttribute = function (attribute, schema, schemaRoot) {
+    var processAttribute = function (attribute, schema) {
         // attribute is the string returned from getAttribute on the XML
         // schema is the <xs:attribute> node
-        if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined) {
+        if (schema.getAttribute('name') === null && schema.getAttribute('ref') !== undefined) {
             schema = schemaRoot.getAllElementsByName(schema.getAttribute('ref'))[0];
         }
         var defaultOpt = schema.getAttribute('default');
-        if (attribute == null) {
+        if (attribute === null) {
             attribute = defaultOpt;
         }
         var dataType = schema.getAttribute('type');
@@ -51,7 +53,7 @@
                 dataType = "string";
             }
         }
-        if (attribute == null) {
+        if (attribute === null) {
             return attribute;
         }
         switch (dataType) {
@@ -71,7 +73,6 @@
             case "short":
                 attribute = Number(attribute);
                 break;
-            case "string":
             default:
                 attribute = String(attribute);
                 break;
@@ -79,26 +80,44 @@
         return attribute;
     };
 
+    this.processSchema = function (schemaXSD) {
+        if (schemaRoot === undefined) {
+            schemaString = schemaXSD;
+            var parse = new DOMParser();
+            schemaRoot = parse.parseFromString(schemaString, 'text/xml');
+            Object.defineProperties(this, {
+                'schema': {
+                    'value': schemaRoot
+                },
+                'schemaString': {
+                    'value': schemaString
+                }
+            });
+        }
+    };
+    this.getSchema = function () {
+        return schemaRoot;
+    };
+    this.getSchemaString = function () {
+        return schemaString;
+    };
+
     this.decode = function (projectXML) {
+        schemaRoot = this.schema;
         this.errors = [];
         // projectXML - DOM Parsed document
         this.projectXML = projectXML.childNodes[0];
         var setupNode = projectXML.getElementsByTagName('setup')[0];
-        var schemaSetup = this.schema.getAllElementsByName('setup')[0];
+        var schemaSetup = schemaRoot.getAllElementsByName('setup')[0];
         // First decode the attributes
         var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
-        for (var i = 0; i < attributes.length; i++) {
+        var i;
+        for (i = 0; i < attributes.length; i++) {
             var attributeName = attributes[i].getAttribute('name') || attributes[i].getAttribute('ref');
             var projectAttr = setupNode.getAttribute(attributeName);
-            projectAttr = this.processAttribute(projectAttr, attributes[i], this.schema);
-            switch (typeof projectAttr) {
-                case "number":
-                case "boolean":
-                    eval('this.' + attributeName + ' = ' + projectAttr);
-                    break;
-                case "string":
-                    eval('this.' + attributeName + ' = "' + projectAttr + '"');
-                    break;
+            projectAttr = processAttribute(projectAttr, attributes[i]);
+            if (projectAttr !== null) {
+                this[attributeName] = projectAttr;
             }
 
         }
@@ -114,7 +133,7 @@
 
         // Now process the survey node options
         var survey = setupNode.getElementsByTagName('survey');
-        for (var i = 0; i < survey.length; i++) {
+        for (i = 0; i < survey.length; i++) {
             var location = survey[i].getAttribute('location');
             switch (location) {
                 case 'pre':
@@ -135,7 +154,7 @@
             this.errors.push("Only one <interface> node in the <setup> node allowed! Others except first ingnored!");
         }
         this.interfaces = new this.interfaceNode(this);
-        if (interfaceNode.length != 0) {
+        if (interfaceNode.length !== 0) {
             interfaceNode = interfaceNode[0];
             this.interfaces.decode(this, interfaceNode, this.schema.getAllElementsByName('interface')[1]);
         }
@@ -143,7 +162,7 @@
         // Page tags
         var pageTags = projectXML.getElementsByTagName('page');
         var pageSchema = this.schema.getAllElementsByName('page')[0];
-        for (var i = 0; i < pageTags.length; i++) {
+        for (i = 0; i < pageTags.length; i++) {
             var node = new this.page(this);
             node.decode(this, pageTags[i], pageSchema);
             this.pages.push(node);
@@ -157,21 +176,21 @@
         root.setAttribute("xsi:noNamespaceSchemaLocation", "test-schema.xsd");
         // Build setup node
         var setup = RootDocument.createElement("setup");
-        var schemaSetup = this.schema.getAllElementsByName('setup')[0];
+        var schemaSetup = schemaRoot.getAllElementsByName('setup')[0];
         // First decode the attributes
         var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
         for (var i = 0; i < attributes.length; i++) {
             var name = attributes[i].getAttribute("name");
-            if (name == undefined) {
+            if (name === undefined) {
                 name = attributes[i].getAttribute("ref");
             }
-            if (eval("this." + name + " != undefined") || attributes[i].getAttribute("use") == "required") {
-                eval("setup.setAttribute('" + name + "',this." + name + ")");
+            if (this[name] !== undefined || attributes[i].getAttribute("use") == "required") {
+                setup.setAttribute(name, this[name]);
             }
         }
         root.appendChild(setup);
         // Survey node
-        if (this.exitText != null) {
+        if (this.exitText !== null) {
             var exitTextNode = RootDocument.createElement('exitText');
             exitTextNode.textContent = this.exitText;
             setup.appendChild(exitTextNode);
@@ -180,17 +199,17 @@
         setup.appendChild(this.postTest.encode(RootDocument));
         setup.appendChild(this.metrics.encode(RootDocument));
         setup.appendChild(this.interfaces.encode(RootDocument));
-        for (var page of this.pages) {
+        this.pages.forEach(function (page) {
             root.appendChild(page.encode(RootDocument));
-        }
+        });
         return RootDocument;
     };
 
     this.surveyNode = function (specification) {
-        this.location = null;
+        this.location = undefined;
         this.options = [];
-        this.parent = null;
-        this.schema = specification.schema.getAllElementsByName('survey')[0];
+        this.parent = undefined;
+        this.schema = schemaRoot.getAllElementsByName('survey')[0];
         this.specification = specification;
 
         this.OptionNode = function (specification) {
@@ -208,23 +227,18 @@
             this.conditions = [];
 
             this.decode = function (parent, child) {
-                this.schema = specification.schema.getAllElementsByName(child.nodeName)[0];
+                this.schema = schemaRoot.getAllElementsByName(child.nodeName)[0];
                 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
-                for (var i in attributeMap) {
-                    if (isNaN(Number(i)) == true) {
+                var i;
+                for (i in attributeMap) {
+                    if (isNaN(Number(i)) === true) {
                         break;
                     }
                     var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
                     var projectAttr = child.getAttribute(attributeName);
-                    projectAttr = parent.processAttribute(projectAttr, attributeMap[i], parent.schema);
-                    switch (typeof projectAttr) {
-                        case "number":
-                        case "boolean":
-                            eval('this.' + attributeName + ' = ' + projectAttr);
-                            break;
-                        case "string":
-                            eval('this.' + attributeName + ' = "' + projectAttr + '"');
-                            break;
+                    projectAttr = processAttribute(projectAttr, attributeMap[i]);
+                    if (projectAttr !== null) {
+                        this[attributeName] = projectAttr;
                     }
                 }
                 if (child.nodeName == 'surveyentry') {
@@ -239,13 +253,13 @@
                 this.statement = child.getElementsByTagName('statement')[0].textContent;
                 if (this.type == "checkbox" || this.type == "radio") {
                     var children = child.getElementsByTagName('option');
-                    if (children.length == null) {
+                    if (children.length === null) {
                         console.log('Malformed' + child.nodeName + 'entry');
                         this.statement = 'Malformed' + child.nodeName + 'entry';
                         this.type = 'statement';
                     } else {
                         this.options = [];
-                        for (var i = 0; i < children.length; i++) {
+                        for (i = 0; i < children.length; i++) {
                             this.options.push({
                                 name: children[i].getAttribute('name'),
                                 text: children[i].textContent
@@ -265,14 +279,14 @@
                     }
                 }
                 var conditionElements = child.getElementsByTagName("conditional");
-                for (var i = 0; i < conditionElements.length; i++) {
+                for (i = 0; i < conditionElements.length; i++) {
                     var condition = conditionElements[i];
                     var obj = {
                         check: condition.getAttribute("check"),
                         value: condition.getAttribute("value"),
                         jumpToOnPass: condition.getAttribute("jumpToOnPass"),
                         jumpToOnFail: condition.getAttribute("jumpToOnFail")
-                    }
+                    };
                     this.conditions.push(obj);
                 }
             };
@@ -283,28 +297,28 @@
                 statement.textContent = this.statement;
                 node.appendChild(statement);
                 node.id = this.id;
-                if (this.name != undefined) {
+                if (this.name !== undefined) {
                     node.setAttribute("name", this.name);
                 }
-                if (this.mandatory != undefined) {
+                if (this.mandatory !== undefined) {
                     node.setAttribute("mandatory", this.mandatory);
                 }
                 node.id = this.id;
-                if (this.name != undefined) {
+                if (this.name !== undefined) {
                     node.setAttribute("name", this.name);
                 }
                 switch (this.type) {
                     case "checkbox":
-                        if (this.min != undefined) {
+                        if (this.min !== undefined) {
                             node.setAttribute("min", this.min);
                         } else {
                             node.setAttribute("min", "0");
                         }
-                        if (this.max != undefined) {
+                        if (this.max !== undefined) {
                             node.setAttribute("max", this.max);
                         } else {
                             node.setAttribute("max", "undefined");
-                        }
+                        } /* falls through */
                     case "radio":
                         for (var i = 0; i < this.options.length; i++) {
                             var option = this.options[i];
@@ -315,27 +329,27 @@
                         }
                         break;
                     case "number":
-                        if (this.min != undefined) {
+                        if (this.min !== undefined) {
                             node.setAttribute("min", this.min);
                         }
-                        if (this.max != undefined) {
+                        if (this.max !== undefined) {
                             node.setAttribute("max", this.max);
                         }
                         break;
                     case "question":
-                        if (this.boxsize != undefined) {
+                        if (this.boxsize !== undefined) {
                             node.setAttribute("boxsize", this.boxsize);
                         }
-                        if (this.mandatory != undefined) {
+                        if (this.mandatory !== undefined) {
                             node.setAttribute("mandatory", this.mandatory);
                         }
                         break;
                     case "video":
-                        if (this.mandatory != undefined) {
+                        if (this.mandatory !== undefined) {
                             node.setAttribute("mandatory", this.mandatory);
-                        }
+                        } /* falls through */
                     case "youtube":
-                        if (this.url != undefined) {
+                        if (this.url !== undefined) {
                             node.setAttribute("url", this.url);
                         }
                         break;
@@ -352,17 +366,18 @@
                             maxText.textContent = this.rightText;
                             node.appendChild(maxText);
                         }
+                        break;
                     default:
                         break;
                 }
-                for (var condition of this.conditions) {
+                this.conditions.forEach(function (condition) {
                     var conditionDOM = doc.createElement("conditional");
                     conditionDOM.setAttribute("check", condition.check);
                     conditionDOM.setAttribute("value", condition.value);
                     conditionDOM.setAttribute("jumpToOnPass", condition.jumpToOnPass);
                     conditionDOM.setAttribute("jumpToOnFail", condition.jumpToOnFail);
                     node.appendChild(conditionDOM);
-                }
+                });
                 return node;
             };
         };
@@ -374,14 +389,14 @@
             } else if (this.location == 'after') {
                 this.location = 'post';
             }
-            var child = xml.firstElementChild
+            var child = xml.firstElementChild;
             while (child) {
                 var node = new this.OptionNode(this.specification);
                 node.decode(parent, child);
                 this.options.push(node);
                 child = child.nextElementSibling;
             }
-            if (this.options.length == 0) {
+            if (this.options.length === 0) {
                 console.log("Empty survey node");
                 console.log(this);
             }
@@ -397,11 +412,11 @@
     };
 
     this.interfaceNode = function (specification) {
-        this.title = null;
-        this.name = null;
+        this.title = undefined;
+        this.name = undefined;
         this.options = [];
         this.scales = [];
-        this.schema = specification.schema.getAllElementsByName('interface')[1];
+        this.schema = schemaRoot.getAllElementsByName('interface')[1];
 
         this.decode = function (parent, xml) {
             this.name = xml.getAttribute('name');
@@ -413,25 +428,16 @@
             // Extract interfaceoption node schema
             var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
             var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
-            for (var i = 0; i < interfaceOptionNodes.length; i++) {
+            var i, j;
+            for (i = 0; i < interfaceOptionNodes.length; i++) {
                 var ioNode = interfaceOptionNodes[i];
                 var option = {};
-                for (var j = 0; j < attributeMap.length; j++) {
+                for (j = 0; j < attributeMap.length; j++) {
                     var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
                     var projectAttr = ioNode.getAttribute(attributeName);
-                    if (parent.processAttribute) {
-                        parent.processAttribute(projectAttr, attributeMap[j], parent.schema)
-                    } else {
-                        parent.parent.processAttribute(projectAttr, attributeMap[j], parent.parent.schema)
-                    }
-                    switch (typeof projectAttr) {
-                        case "number":
-                        case "boolean":
-                            eval('option.' + attributeName + ' = ' + projectAttr);
-                            break;
-                        case "string":
-                            eval('option.' + attributeName + ' = "' + projectAttr + '"');
-                            break;
+                    projectAttr = processAttribute(projectAttr, attributeMap[j]);
+                    if (projectAttr !== null) {
+                        option[attributeName] = projectAttr;
                     }
                 }
                 this.options.push(option);
@@ -442,7 +448,7 @@
             if (scaleParent.length == 1) {
                 scaleParent = scaleParent[0];
                 var scalelabels = scaleParent.getAllElementsByTagName('scalelabel');
-                for (var i = 0; i < scalelabels.length; i++) {
+                for (i = 0; i < scalelabels.length; i++) {
                     this.scales.push({
                         text: scalelabels[i].textContent,
                         position: Number(scalelabels[i].getAttribute('position'))
@@ -453,27 +459,27 @@
 
         this.encode = function (doc) {
             var node = doc.createElement("interface");
-            if (typeof name == "string" && name.length > 0)
+            if (typeof this.name == "string" && this.name.length > 0)
                 node.setAttribute("name", this.name);
             if (typeof this.title == "string") {
                 var titleNode = doc.createElement("title");
                 titleNode.textContent = this.title;
                 node.appendChild(titleNode);
             }
-            for (var option of this.options) {
+            this.options.forEach(function (option) {
                 var child = doc.createElement("interfaceoption");
                 child.setAttribute("type", option.type);
                 child.setAttribute("name", option.name);
                 node.appendChild(child);
-            }
-            if (this.scales.length != 0) {
+            });
+            if (this.scales.length !== 0) {
                 var scales = doc.createElement("scales");
-                for (var scale of this.scales) {
+                this.scales.forEach(function (scale) {
                     var child = doc.createElement("scalelabel");
                     child.setAttribute("position", scale.position);
                     child.textContent = scale.text;
                     scales.appendChild(child);
-                }
+                });
                 node.appendChild(scales);
             }
             return node;
@@ -485,16 +491,16 @@
         this.decode = function (parent, xml) {
             var children = xml.getElementsByTagName('metricenable');
             for (var i in children) {
-                if (isNaN(Number(i)) == true) {
+                if (isNaN(Number(i)) === true) {
                     break;
                 }
                 this.enabled.push(children[i].textContent);
             }
-        }
+        };
         this.encode = function (doc) {
             var node = doc.createElement('metric');
             for (var i in this.enabled) {
-                if (isNaN(Number(i)) == true) {
+                if (isNaN(Number(i)) === true) {
                     break;
                 }
                 var child = doc.createElement('metricenable');
@@ -502,8 +508,8 @@
                 node.appendChild(child);
             }
             return node;
-        }
-    }
+        };
+    };
 
     this.page = function (specification) {
         this.presentedId = undefined;
@@ -512,57 +518,52 @@
         this.hostURL = undefined;
         this.randomiseOrder = undefined;
         this.loop = undefined;
-        this.outsideReference = null;
-        this.loudness = null;
-        this.label = null;
-        this.labelStart = "";
-        this.preTest = null;
-        this.postTest = null;
+        this.outsideReference = undefined;
+        this.loudness = undefined;
+        this.label = undefined;
+        this.labelStart = undefined;
+        this.preTest = undefined;
+        this.postTest = undefined;
         this.interfaces = [];
-        this.playOne = null;
-        this.restrictMovement = null;
-        this.position = null;
+        this.playOne = undefined;
+        this.restrictMovement = undefined;
+        this.position = undefined;
         this.commentBoxPrefix = "Comment on track";
         this.audioElements = [];
         this.commentQuestions = [];
-        this.schema = specification.schema.getAllElementsByName("page")[0];
+        this.schema = schemaRoot.getAllElementsByName("page")[0];
         this.specification = specification;
-        this.parent = null;
+        this.parent = undefined;
 
         this.decode = function (parent, xml) {
             this.parent = parent;
             var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
-            for (var i = 0; i < attributeMap.length; i++) {
+            var i, node;
+            for (i = 0; i < attributeMap.length; i++) {
                 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
                 var projectAttr = xml.getAttribute(attributeName);
-                projectAttr = parent.processAttribute(projectAttr, attributeMap[i], parent.schema);
-                switch (typeof projectAttr) {
-                    case "number":
-                    case "boolean":
-                        eval('this.' + attributeName + ' = ' + projectAttr);
-                        break;
-                    case "string":
-                        eval('this.' + attributeName + ' = "' + projectAttr + '"');
-                        break;
+                projectAttr = processAttribute(projectAttr, attributeMap[i]);
+                if (projectAttr !== null) {
+                    this[attributeName] = projectAttr;
                 }
             }
 
             // Get the title
             var title = xml.getElementsByTagName('title');
-            if (title.length != 0 && title[0].parentElement == xml) {
+            if (title.length !== 0 && title[0].parentElement == xml) {
                 this.title = title[0].textContent;
             }
 
             // Get the Comment Box Prefix
             var CBP = xml.getElementsByTagName('commentboxprefix');
-            if (CBP.length != 0 && CBP[0].parentElement == xml) {
+            if (CBP.length !== 0 && CBP[0].parentElement == xml) {
                 this.commentBoxPrefix = CBP[0].textContent;
             }
 
             // Now decode the interfaces
             var interfaceNode = xml.getElementsByTagName('interface');
-            for (var i = 0; i < interfaceNode.length; i++) {
-                var node = new parent.interfaceNode(this.specification);
+            for (i = 0; i < interfaceNode.length; i++) {
+                node = new parent.interfaceNode(this.specification);
                 node.decode(this, interfaceNode[i], parent.schema.getAllElementsByName('interface')[1]);
                 this.interfaces.push(node);
             }
@@ -570,17 +571,17 @@
             // Now process the survey node options
             var survey = xml.getElementsByTagName('survey');
             var surveySchema = parent.schema.getAllElementsByName('survey')[0];
-            for (var i = 0; i < survey.length; i++) {
+            for (i = 0; i < survey.length; i++) {
                 var location = survey[i].getAttribute('location');
                 if (location == 'pre' || location == 'before') {
-                    if (this.preTest != null) {
+                    if (this.preTest !== undefined) {
                         this.errors.push("Already a pre/before test survey defined! Ignoring second!!");
                     } else {
                         this.preTest = new parent.surveyNode(this.specification);
                         this.preTest.decode(parent, survey[i], surveySchema);
                     }
                 } else if (location == 'post' || location == 'after') {
-                    if (this.postTest != null) {
+                    if (this.postTest !== undefined) {
                         this.errors.push("Already a post/after test survey defined! Ignoring second!!");
                     } else {
                         this.postTest = new parent.surveyNode(this.specification);
@@ -591,19 +592,19 @@
 
             // Now process the audioelement tags
             var audioElements = xml.getElementsByTagName('audioelement');
-            for (var i = 0; i < audioElements.length; i++) {
-                var node = new this.audioElementNode(this.specification);
-                node.decode(this, audioElements[i]);
-                this.audioElements.push(node);
+            for (i = 0; i < audioElements.length; i++) {
+                var audioNode = new this.audioElementNode(this.specification);
+                audioNode.decode(this, audioElements[i]);
+                this.audioElements.push(audioNode);
             }
 
             // Now decode the commentquestions
             var cqNode = xml.getElementsByTagName('commentquestions');
-            if (cqNode.length != 0) {
+            if (cqNode.length !== 0) {
                 cqNode = cqNode[0];
                 var commentQuestion = cqNode.firstElementChild;
                 while (commentQuestion) {
-                    var node = new this.commentQuestionNode(this.specification);
+                    node = new this.commentQuestionNode(this.specification);
                     node.decode(parent, commentQuestion);
                     this.commentQuestions.push(node);
                     commentQuestion = commentQuestion.nextElementSibling;
@@ -615,32 +616,30 @@
             var AHNode = root.createElement("page");
             // First decode the attributes
             var attributes = this.schema.getAllElementsByTagName('xs:attribute');
-            for (var i = 0; i < attributes.length; i++) {
+            var i;
+            for (i = 0; i < attributes.length; i++) {
                 var name = attributes[i].getAttribute("name");
-                if (name == undefined) {
+                if (name === null) {
                     name = attributes[i].getAttribute("ref");
                 }
-                if (eval("this." + name + " != undefined") || attributes[i].getAttribute("use") == "required") {
-                    eval("AHNode.setAttribute('" + name + "',this." + name + ")");
+                if (this[name] !== undefined || attributes[i].getAttribute("use") == "required") {
+                    AHNode.setAttribute(name, this[name]);
                 }
             }
-            if (this.loudness != null) {
-                AHNode.setAttribute("loudness", this.loudness);
-            }
             // <commentboxprefix>
             var commentboxprefix = root.createElement("commentboxprefix");
             commentboxprefix.textContent = this.commentBoxPrefix;
             AHNode.appendChild(commentboxprefix);
 
-            for (var i = 0; i < this.interfaces.length; i++) {
+            for (i = 0; i < this.interfaces.length; i++) {
                 AHNode.appendChild(this.interfaces[i].encode(root));
             }
 
-            for (var i = 0; i < this.audioElements.length; i++) {
+            for (i = 0; i < this.audioElements.length; i++) {
                 AHNode.appendChild(this.audioElements[i].encode(root));
             }
             // Create <CommentQuestion>
-            for (var i = 0; i < this.commentQuestions.length; i++) {
+            for (i = 0; i < this.commentQuestions.length; i++) {
                 AHNode.appendChild(this.commentQuestions[i].encode(root));
             }
 
@@ -650,14 +649,17 @@
         };
 
         this.commentQuestionNode = function (specification) {
-            this.id = null;
+            this.id = undefined;
             this.name = undefined;
             this.type = undefined;
             this.statement = undefined;
-            this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
+            this.schema = schemaRoot.getAllElementsByName('commentquestion')[0];
             this.decode = function (parent, xml) {
                 this.id = xml.id;
                 this.name = xml.getAttribute('name');
+                if (this.name === null) {
+                    this.name = undefined;
+                }
                 switch (xml.nodeName) {
                     case "commentradio":
                         this.type = "radio";
@@ -674,9 +676,10 @@
                         this.step = undefined;
                         break;
                     case "commentquestion":
-                    default:
                         this.type = "question";
                         break;
+                    default:
+                        throw ("Unknown comment type " + xml.nodeName);
                 }
                 this.statement = xml.getElementsByTagName('statement')[0].textContent;
                 if (this.type == "radio" || this.type == "checkbox") {
@@ -693,12 +696,12 @@
                     this.min = Number(xml.getAttribute("min"));
                     this.max = Number(xml.getAttribute("max"));
                     this.step = Number(xml.getAttribute("step"));
-                    if (this.step == undefined) {
+                    if (this.step === undefined) {
                         this.step = 1;
                     }
                     this.value = Number(xml.getAttribute("value"));
-                    if (this.value == undefined) {
-                        this.value = min;
+                    if (this.value === undefined) {
+                        this.value = this.min;
                     }
                     this.leftText = xml.getElementsByTagName("minText");
                     if (this.leftText && this.leftText.length > 0) {
@@ -728,25 +731,26 @@
                         node = root.createElement("commentslider");
                         break;
                     case "question":
-                    default:
                         node = root.createElement("commentquestion");
                         break;
+                    default:
+                        throw ("Unknown type " + this.type);
                 }
                 node.id = this.id;
                 node.setAttribute("type", this.type);
-                if (this.name != undefined) {
+                if (this.name !== undefined) {
                     node.setAttribute("name", this.name);
                 }
                 var statement = root.createElement("statement");
                 statement.textContent = this.statement;
                 node.appendChild(statement);
                 if (this.type == "radio" || this.type == "checkbox") {
-                    for (var option of this.options) {
+                    this.options.forEach(function (option) {
                         var child = root.createElement("option");
                         child.setAttribute("name", option.name);
                         child.textContent = option.text;
                         node.appendChild(child);
-                    }
+                    });
                 }
                 if (this.type == "slider") {
                     node.setAttribute("min", this.min);
@@ -773,36 +777,30 @@
         };
 
         this.audioElementNode = function (specification) {
-            this.url = null;
-            this.id = null;
-            this.name = null;
-            this.parent = null;
-            this.type = null;
-            this.marker = null;
+            this.url = undefined;
+            this.id = undefined;
+            this.name = undefined;
+            this.parent = undefined;
+            this.type = undefined;
+            this.marker = undefined;
             this.enforce = false;
             this.gain = 0.0;
-            this.label = null;
+            this.label = undefined;
             this.startTime = undefined;
             this.stopTime = undefined;
             this.sampleRate = undefined;
             this.alternatives = [];
-            this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
-            this.parent = null;
+            this.schema = schemaRoot.getAllElementsByName('audioelement')[0];
+            this.parent = undefined;
             this.decode = function (parent, xml) {
                 this.parent = parent;
                 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
                 for (var i = 0; i < attributeMap.length; i++) {
                     var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
                     var projectAttr = xml.getAttribute(attributeName);
-                    projectAttr = parent.parent.processAttribute(projectAttr, attributeMap[i], parent.parent.schema);
-                    switch (typeof projectAttr) {
-                        case "number":
-                        case "boolean":
-                            eval('this.' + attributeName + ' = ' + projectAttr);
-                            break;
-                        case "string":
-                            eval('this.' + attributeName + ' = "' + projectAttr + '"');
-                            break;
+                    projectAttr = processAttribute(projectAttr, attributeMap[i]);
+                    if (projectAttr !== null) {
+                        this[attributeName] = projectAttr;
                     }
                 }
                 // Get the alternative nodes
@@ -823,11 +821,11 @@
                 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
                 for (var i = 0; i < attributes.length; i++) {
                     var name = attributes[i].getAttribute("name");
-                    if (name == undefined) {
+                    if (name === null) {
                         name = attributes[i].getAttribute("ref");
                     }
-                    if (eval("this." + name + " != undefined") || attributes[i].getAttribute("use") == "required") {
-                        eval("AENode.setAttribute('" + name + "',this." + name + ")");
+                    if (this[name] !== undefined || attributes[i].getAttribute("use") == "required") {
+                        AENode.setAttribute(name, this[name]);
                     }
                 }
                 this.alternatives.forEach(function (alt) {