changeset 2892:b2f52634c830

Merge branch 'master' of https://github.com/BrechtDeMan/WebAudioEvaluationTool
author www-data <www-data@sucuk.dcs.qmul.ac.uk>
date Wed, 05 Jul 2017 12:22:29 +0100
parents e61531ce764f (current diff) 22d1f6d9f0bf (diff)
children 7c21a0db02e9
files test_create/custom.css
diffstat 23 files changed, 2884 insertions(+), 2632 deletions(-) [+]
line wrap: on
line diff
--- a/css/core.css	Tue Jun 27 21:22:15 2017 +0100
+++ b/css/core.css	Wed Jul 05 12:22:29 2017 +0100
@@ -8,6 +8,12 @@
     margin-bottom: 10px;
     font-size: 2em;
 }
+div#footer {
+    position: fixed;
+    bottom: 0px;
+    width: 100%;
+    text-align: center;
+}
 div.indicator-box {
     position: absolute;
     left: 150px;
--- a/index.html	Tue Jun 27 21:22:15 2017 +0100
+++ b/index.html	Wed Jul 05 12:22:29 2017 +0100
@@ -24,7 +24,7 @@
 
 <body>
     <div id='topLevelBody'>
-        <h1>Web Audio Evaluation Tool</h1>
+        <h1><a target="_blank" href="https://github.com/BrechtDeMan/WebAudioEvaluationTool">Web Audio Evaluation Toolbox (v1.2.1)</a></h1>
         <h2>Start menu </h2>
         <ul>
             <li><a href="test.html?url=tests/examples/project.xml" target="_blank">APE interface test example</a></li>
@@ -51,6 +51,7 @@
         <button id="popup-previous" class="popupButton">Back</button>
     </div>
     <div class="testHalt" style="visibility: hidden"></div>
+    <div id="footer"><a target="_blank" href="https://github.com/BrechtDeMan/WebAudioEvaluationTool">Web Audio Evaluation Toolbox (v1.2.1)</a></div>
 </body>
 
 </html>
--- a/interfaces/ABX.js	Tue Jun 27 21:22:15 2017 +0100
+++ b/interfaces/ABX.js	Wed Jul 05 12:22:29 2017 +0100
@@ -14,7 +14,7 @@
     interfaceContext.insertPoint.innerHTML = ""; // Clear the current schema
 
     // Custom comparator Object
-    Interface.prototype.comparator = null;
+    interfaceContext.comparator = null;
 
     // The injection point into the HTML page
     interfaceContext.insertPoint = document.getElementById("topLevelBody");
@@ -375,7 +375,7 @@
         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);
+    var element = page.addAudioElement();
     for (var atr in page.audioElements[elementId]) {
         element[atr] = page.audioElements[elementId][atr];
     }
--- a/interfaces/horizontal-sliders.css	Tue Jun 27 21:22:15 2017 +0100
+++ b/interfaces/horizontal-sliders.css	Wed Jul 05 12:22:29 2017 +0100
@@ -7,23 +7,19 @@
     /* Set the background colour (note US English spelling) to grey*/
     background-color: #ddd
 }
-
 div.pageTitle {
     width: auto;
     height: 20px;
     margin: 10px 0px;
 }
-
 div.pageTitle span {
     font-size: 1.5em;
 }
-
 button {
     /* Specify any button structure or style */
     min-width: 20px;
     background-color: #ddd
 }
-
 div#slider-holder {
     height: inherit;
     position: absolute;
@@ -31,29 +27,24 @@
     z-index: 3;
     margin-top: 25px;
 }
-
 div#scale-holder {
     height: inherit;
     position: absolute;
     left: 0px;
     z-index: 2;
 }
-
 div#scale-text-holder {
     position: relative;
     float: left;
 }
-
 div.scale-text {
     position: absolute;
     font-size: 1.2em;
 }
-
 canvas#scale-canvas {
     position: relative;
     float: left;
 }
-
 div.track-slider {
     float: left;
     height: 94px;
@@ -64,53 +55,44 @@
     margin-left: 94px;
     margin-bottom: 25px;
 }
-
 div.track-slider-title {
     float: left;
     padding-top: 40px;
     width: 100px;
 }
-
 button.track-slider-button {
     float: left;
     width: 100px;
     height: 94px;
 }
-
 div#outside-reference-holder {
     display: flex;
     align-content: center;
     justify-content: center;
     margin-bottom: 5px;
 }
-
 button.outside-reference {
     position: inherit;
     margin: 0px 5px;
 }
-
 div.track-slider-playing {
-    background-color: #FFDDDD;
+    background-color: rgba(255, 201, 201, 0.5);
 }
-
 input.track-slider-range {
     float: left;
     margin: 2px 10px;
 }
-
 input[type=range] {
     height: 94px;
     padding: 0px;
     color: rgb(255, 144, 144);
 }
-
 input[type=range]::-webkit-slider-runnable-track {
     cursor: pointer;
     background: #fff;
     border-radius: 4px;
     border: 1px solid #000;
 }
-
 input[type=range]::-moz-range-track {
     height: 8px;
     cursor: pointer;
@@ -118,26 +100,21 @@
     border-radius: 4px;
     border: 1px solid #000;
 }
-
 input.track-slider-not-moved[type=range]::-webkit-slider-runnable-track {
     background: #aaa;
 }
-
 input.track-slider-not-moved[type=range]::-moz-range-track {
     background: #aaa;
 }
-
 div#page-count {
     float: left;
     margin: 0px 5px;
 }
-
 div#master-volume-holder {
     position: absolute;
     top: 10px;
     left: 120px;
 }
-
 div.comment-box-playing {
     background-color: #FFDDDD;
 }
--- a/interfaces/horizontal-sliders.js	Tue Jun 27 21:22:15 2017 +0100
+++ b/interfaces/horizontal-sliders.js	Wed Jul 05 12:22:29 2017 +0100
@@ -237,6 +237,7 @@
     // 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( )
+    var playing = false;
     this.parent = audioObject;
 
     this.holder = document.createElement('div');
@@ -275,9 +276,9 @@
     this.play.onclick = function (event) {
         var id = Number(event.currentTarget.value);
         //audioEngineContext.metric.sliderPlayed(id);
-        if (event.currentTarget.getAttribute("playstate") == "ready") {
+        if (!playing) {
             audioEngineContext.play(id);
-        } else if (event.currentTarget.getAttribute("playstate") == "playing") {
+        } else if (playing) {
             audioEngineContext.stop();
         }
     };
@@ -296,7 +297,8 @@
     };
     this.startPlayback = function () {
         // Called when playback has begun
-        this.play.setAttribute("playstate", "playing");
+        playing = true;
+        this.play.textContent = "Stop";
         $(".track-slider").removeClass('track-slider-playing');
         $(this.holder).addClass('track-slider-playing');
         var outsideReference = document.getElementById('outside-reference');
@@ -310,7 +312,8 @@
     };
     this.stopPlayback = function () {
         // Called when playback has stopped. This gets called even if playback never started!
-        this.play.setAttribute("playstate", "ready");
+        playing = false;
+        this.play.textContent = "Play";
         $(this.holder).removeClass('track-slider-playing');
         var box = interfaceContext.commentBoxes.boxes.find(function (a) {
             return a.id === audioObject.id;
--- a/interfaces/interfaces.json	Tue Jun 27 21:22:15 2017 +0100
+++ b/interfaces/interfaces.json	Wed Jul 05 12:22:29 2017 +0100
@@ -30,6 +30,10 @@
             "name": "timeline",
             "scripts": ["interfaces/timeline.js"],
             "css": ["interfaces/timeline.css"]
+        }, {
+            "name": "ordinal",
+            "scripts": ["interfaces/ordinal.js"],
+            "css": ["interfaces/ordinal.css"]
         }
     ]
 }
--- a/interfaces/mushra.css	Tue Jun 27 21:22:15 2017 +0100
+++ b/interfaces/mushra.css	Wed Jul 05 12:22:29 2017 +0100
@@ -7,52 +7,43 @@
     /* Set the background colour (note US English spelling) to grey*/
     background-color: #ddd
 }
-
 div.pageTitle {
     width: auto;
     height: 20px;
     margin: 10px 0px;
 }
-
 div.pageTitle span {
     font-size: 1.5em;
 }
-
 button {
     /* Specify any button structure or style */
     min-width: 20px;
     background-color: #ddd
 }
-
 div#slider-holder {
     height: inherit;
     position: absolute;
     left: 0px;
     z-index: 3;
 }
-
 div#scale-holder {
     height: inherit;
     position: absolute;
     left: 0px;
     z-index: 2;
 }
-
 div#scale-text-holder {
     position: relative;
     width: 100px;
     float: left;
 }
-
 div.scale-text {
     position: absolute;
 }
-
 canvas#scale-canvas {
     position: relative;
     float: left;
 }
-
 div.track-slider {
     float: left;
     width: 94px;
@@ -62,27 +53,22 @@
     padding: 2px;
     margin-left: 50px;
 }
-
 div#outside-reference-holder {
     display: flex;
     align-content: center;
     justify-content: center;
     margin-bottom: 5px;
 }
-
 button.outside-reference {
     position: inherit;
     margin: 0px 5px;
 }
-
 div.track-slider-playing {
-    background-color: #FFDDDD;
+    background-color: rgba(255, 201, 201, 0.5);
 }
-
 input.track-slider-range {
     margin: 2px 0px;
 }
-
 input[type=range][orient=vertical] {
     writing-mode: bt-lr;
     /* IE */
@@ -92,7 +78,6 @@
     padding: 0 5px;
     color: rgb(255, 144, 144);
 }
-
 input[type=range]::-webkit-slider-runnable-track {
     width: 8px;
     cursor: pointer;
@@ -100,7 +85,6 @@
     border-radius: 4px;
     border: 1px solid #000;
 }
-
 input[type=range]::-moz-range-track {
     width: 8px;
     cursor: pointer;
@@ -108,79 +92,63 @@
     border-radius: 4px;
     border: 1px solid #000;
 }
-
 input[type=range]::-ms-track {
     cursor: pointer;
     background: #fff;
     border-radius: 4px;
     border: 1px solid #000;
 }
-
 input.track-slider-not-moved[type=range]::-webkit-slider-runnable-track {
     background: #aaa;
 }
-
 input.track-slider-not-moved[type=range]::-moz-range-track {
     background: #aaa;
 }
-
 input[type=range]::-moz-range-thumb {
     margin-left: -7px;
     cursor: pointer;
     margin-top: -1px;
     box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
 }
-
 input[type=range]::-webkit-slider-thumb {
     cursor: pointer;
     margin-top: -1px;
     margin-left: -4px;
 }
-
 input[type=range]::-ms-thumb {
     cursor: pointer;
     margin-top: -1px;
     margin-left: -4px;
 }
-
 input[type=range]::-ms-tooltip {
     visibility: hidden;
 }
-
 input.track-slider-range-disabled {}
-
 input.track-slider-range-disabled[type=range]::-webkit-slider-runnable-track {
     cursor: not-allowed;
 }
-
 input.track-slider-range-disabled[type=range]::-moz-range-track {
     cursor: not-allowed;
 }
-
 input.track-slider-range-disabled[type=range]::-ms-track {
     cursor: not-allowed;
 }
-
 input.track-slider-range-disabled[type=range]::-moz-range-thumb {
     cursor: not-allowed;
     background-color: #888;
 }
-
 input.track-slider-range-disabled[type=range]::-webkit-slider-thumb {
     cursor: not-allowed;
     background-color: #888;
 }
-
 input.track-slider-range-disabled[type=range]::-ms-thumb {
     cursor: not-allowed;
     background-color: #888;
 }
-
 div#page-count {
     float: left;
     margin: 0px 5px;
 }
-
 div#master-volume-holder {
     position: absolute;
     top: 10px;
--- a/interfaces/mushra.js	Tue Jun 27 21:22:15 2017 +0100
+++ b/interfaces/mushra.js	Wed Jul 05 12:22:29 2017 +0100
@@ -50,7 +50,7 @@
     var playback = document.createElement("button");
     playback.innerHTML = 'Stop';
     playback.id = 'playback-button';
-    playback.style.float = 'left';
+    playback.style.display = 'inline-block';
     // onclick function. Check if it is playing or not, call the correct function in the
     // audioEngine, change the button text to reflect the next state.
     playback.onclick = function () {
@@ -66,7 +66,7 @@
     submit.innerHTML = 'Next';
     submit.onclick = buttonSubmitClick;
     submit.id = 'submit-button';
-    submit.style.float = 'left';
+    submit.style.display = 'inline-block';
     // Append the interface buttons into the interfaceButtons object.
     interfaceButtons.appendChild(playback);
     interfaceButtons.appendChild(submit);
@@ -224,6 +224,7 @@
                     if (pagecountHolder === null) {
                         pagecountHolder = document.createElement('div');
                         pagecountHolder.id = 'page-count';
+                        pagecountHolder.style.display = 'inline-block';
                     }
                     pagecountHolder.innerHTML = '<span>Page ' + (testState.stateIndex + 1) + ' of ' + testState.stateMap.length + '</span>';
                     var inject = document.getElementById('interface-buttons');
@@ -237,6 +238,33 @@
                 case "comments":
                     interfaceContext.commentBoxes.showCommentBoxes(feedbackHolder, true);
                     break;
+                case "fragmentSort":
+                    var button = document.getElementById('sort');
+                    if (button === null) {
+                        button = document.createElement("button");
+                        button.id = 'sort';
+                        button.textContent = "Sort";
+                        button.style.display = 'inline-block';
+                        var container = document.getElementById("interface-buttons");
+                        var neighbour = container.lastElementChild;
+                        container.appendChild(button);
+                        button.onclick = function () {
+                            var sortIndex = interfaceContext.sortFragmentsByScore();
+                            var sliderBox = document.getElementById("slider-holder");
+                            var nodes = audioEngineContext.audioObjects.filter(function (ao) {
+                                return ao.specification.type !== "outside-reference";
+                            });
+                            var i;
+                            nodes.forEach(function (ao) {
+                                sliderBox.removeChild(ao.interfaceDOM.holder);
+                            });
+                            for (i = 0; i < nodes.length; i++) {
+                                var j = sortIndex[i];
+                                sliderBox.appendChild(nodes[j].interfaceDOM.holder);
+                            }
+                        };
+                    }
+                    break;
             }
         }
     });
@@ -374,13 +402,9 @@
         return this.slider.value;
     };
 
-    this.resize = function (event) {
-        var imgHeight = 0;
-        if (document.getElementById("imageController")) {
-            imgHeight = $(interfaceContext.imageHolder.root).height();
-        }
-        this.holder.style.height = window.innerHeight - 200 - imgHeight + 'px';
-        this.slider.style.height = window.innerHeight - 250 - imgHeight + 'px';
+    this.resize = function (event, height) {
+        this.holder.style.height = height - 20 + 'px';
+        this.slider.style.height = height - 70 + 'px';
     };
     this.updateLoading = function (progress) {
         progress = String(progress);
@@ -409,7 +433,9 @@
     // MANDATORY FUNCTION
 
     var outsideRef = document.getElementById('outside-reference'),
-        imageHeight = 0;
+        imageHeight = 0,
+        minHeight = Math.max(Math.floor(window.screen.height * 0.33), 200),
+        maxHeight = Math.floor(window.screen.height * 0.5);
     if (document.getElementById("imageController")) {
         imageHeight = $(interfaceContext.imageHolder.root).height();
     }
@@ -421,18 +447,21 @@
     var numObj = document.getElementsByClassName('track-slider').length;
     var totalWidth = (numObj - 1) * 150 + 100;
     var diff = (window.innerWidth - totalWidth) / 2;
-    document.getElementById('slider').style.height = window.innerHeight - 180 - imageHeight + 'px';
+    var height = window.innerHeight - 180 - imageHeight;
+    height = Math.min(height, maxHeight);
+    height = Math.max(height, minHeight);
+    document.getElementById('slider').style.height = height + 'px';
     if (diff <= 0) {
         diff = 0;
     }
     document.getElementById('slider-holder').style.marginLeft = diff + 'px';
     for (var i in audioEngineContext.audioObjects) {
         if (audioEngineContext.audioObjects[i].specification.type != 'outside-reference') {
-            audioEngineContext.audioObjects[i].interfaceDOM.resize(event);
+            audioEngineContext.audioObjects[i].interfaceDOM.resize(event, height);
         }
     }
     document.getElementById('scale-holder').style.marginLeft = (diff - 100) + 'px';
-    document.getElementById('scale-text-holder').style.height = window.innerHeight - imageHeight - 194 + 'px';
+    document.getElementById('scale-text-holder').style.height = height - 14 + 'px';
     // Cheers edge for making me delete a canvas every resize.
     var canvas = document.getElementById('scale-canvas');
     var new_canvas = document.createElement("canvas");
@@ -440,7 +469,7 @@
     canvas.parentElement.appendChild(new_canvas);
     canvas.parentElement.removeChild(canvas);
     new_canvas.width = totalWidth;
-    new_canvas.height = window.innerHeight - 194 - imageHeight;
+    new_canvas.height = height - 14;
     drawScale();
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interfaces/ordinal.css	Wed Jul 05 12:22:29 2017 +0100
@@ -0,0 +1,36 @@
+[draggable] {
+    -moz-user-select: none;
+    -khtml-user-select: none;
+    -webkit-user-select: none;
+    user-select: none;
+    /* Required to make elements draggable in old WebKit */
+    -khtml-user-drag: element;
+    -webkit-user-drag: element;
+}
+.ordinal-element {
+    width: 250px;
+    height: 250px;
+    background: #bbffbb;
+    border: 2px #050 solid;
+    border-radius: 10px;
+    float: left;
+    margin: 10px 5px;
+    text-align: center;
+    cursor: move;
+}
+.disabled {
+    background-color: grey;
+}
+.playing {
+    background-color: #ffbbbb;
+    border: 2px #500 solid;
+}
+.dragging {
+    opacity: 0.4;
+}
+.over {
+    border-style: dashed;
+}
+.ordinal-element-label {
+    font-size: 2em;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interfaces/ordinal.js	Wed Jul 05 12:22:29 2017 +0100
@@ -0,0 +1,487 @@
+/**
+ * WAET Blank Template
+ * Use this to start building your custom interface
+ */
+/*globals interfaceContext, window, document, specification, audioEngineContext, console, testState, $, storage */
+// Once this is loaded and parsed, begin execution
+loadInterface();
+
+function loadInterface() {
+    // Use this to do any one-time page / element construction. For instance, placing any stationary text objects,
+    // holding div's, or setting up any nodes which are present for the entire test sequence
+
+    // The injection point into the HTML page
+    interfaceContext.insertPoint = document.getElementById("topLevelBody");
+    var testContent = document.createElement('div');
+    testContent.id = 'testContent';
+
+    // Create the top div for the Title element
+    var titleAttr = specification.title;
+    var title = document.createElement('div');
+    title.className = "title";
+    title.align = "center";
+    var titleSpan = document.createElement('span');
+    titleSpan.id = "test-title";
+
+    // Set title to that defined in XML, else set to default
+    if (titleAttr !== undefined) {
+        titleSpan.textContent = titleAttr;
+    } else {
+        titleSpan.textContent = 'Listening test';
+    }
+    // Insert the titleSpan element into the title div element.
+    title.appendChild(titleSpan);
+
+    var pagetitle = document.createElement('div');
+    pagetitle.className = "pageTitle";
+    pagetitle.align = "center";
+
+    titleSpan = document.createElement('span');
+    titleSpan.id = "pageTitle";
+    pagetitle.appendChild(titleSpan);
+
+    // Create Interface buttons!
+    var interfaceButtons = document.createElement('div');
+    interfaceButtons.id = 'interface-buttons';
+    interfaceButtons.style.height = '25px';
+
+    // Create playback start/stop points
+    var playback = document.createElement("button");
+    playback.innerHTML = 'Stop';
+    playback.id = 'playback-button';
+    playback.style.float = 'left';
+    // onclick function. Check if it is playing or not, call the correct function in the
+    // audioEngine, change the button text to reflect the next state.
+    playback.onclick = function () {
+        if (audioEngineContext.status == 1) {
+            audioEngineContext.stop();
+            this.innerHTML = 'Stop';
+            var time = audioEngineContext.timer.getTestTime();
+            console.log('Stopped at ' + time); // DEBUG/SAFETY
+        }
+    };
+    // Create Submit (save) button
+    var submit = document.createElement("button");
+    submit.innerHTML = 'Next';
+    submit.onclick = buttonSubmitClick;
+    submit.id = 'submit-button';
+    submit.style.float = 'left';
+    // Append the interface buttons into the interfaceButtons object.
+    interfaceButtons.appendChild(playback);
+    interfaceButtons.appendChild(submit);
+
+    // Create outside reference holder
+    var outsideRef = document.createElement("div");
+    outsideRef.id = "outside-reference-holder";
+
+    // Create a slider box
+    var slider = document.createElement("div");
+    slider.id = "slider";
+    slider.style.height = "300px";
+
+    // Global parent for the comment boxes on the page
+    var feedbackHolder = document.createElement('div');
+    feedbackHolder.id = 'feedbackHolder';
+
+    testContent.style.zIndex = 1;
+    interfaceContext.insertPoint.innerHTML = ""; // Clear the current schema
+
+    // Inject into HTML
+    testContent.appendChild(title); // Insert the title
+    testContent.appendChild(pagetitle);
+    testContent.appendChild(interfaceButtons);
+    testContent.appendChild(outsideRef);
+    testContent.appendChild(slider);
+    testContent.appendChild(feedbackHolder);
+    interfaceContext.insertPoint.appendChild(testContent);
+
+    // 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
+    var id = page.id;
+
+    var feedbackHolder = document.getElementById('feedbackHolder');
+    feedbackHolder.innerHTML = "";
+
+    var interfaceObj = interfaceContext.getCombinedInterfaces(page);
+    if (interfaceObj.length > 1) {
+        console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
+    }
+    interfaceObj = interfaceObj[0];
+
+    // Set the page title
+    if (typeof page.title == "string" && page.title.length > 0) {
+        document.getElementById("test-title").textContent = page.title;
+    }
+
+    if (interfaceObj.title !== null) {
+        document.getElementById("pageTitle").textContent = interfaceObj.title;
+    }
+
+    if (interfaceObj.image !== undefined) {
+        feedbackHolder.insertBefore(interfaceContext.imageHolder.root, document.getElementById("slider"));
+        interfaceContext.imageHolder.setImage(interfaceObj.image);
+    }
+    // Delete outside reference
+    document.getElementById("outside-reference-holder").innerHTML = "";
+
+    var sliderBox = document.getElementById('slider');
+    sliderBox.innerHTML = "";
+
+    var commentBoxPrefix = "Comment on track";
+    if (interfaceObj.commentBoxPrefix !== undefined) {
+        commentBoxPrefix = interfaceObj.commentBoxPrefix;
+    }
+
+    $(page.commentQuestions).each(function (index, element) {
+        var node = interfaceContext.createCommentQuestion(element);
+        feedbackHolder.appendChild(node.holder);
+    });
+
+    var index = 0;
+    var labelType = page.label;
+    if (labelType == "default") {
+        labelType = "number";
+    }
+    page.audioElements.forEach(function (element, pageIndex) {
+        var audioObject = audioEngineContext.newTrack(element);
+        if (element.type == 'outside-reference') {
+            // Construct outside reference;
+            var orNode = new interfaceContext.outsideReferenceDOM(audioObject, index, document.getElementById("outside-reference-holder"));
+            audioObject.bindInterface(orNode);
+        } else {
+            // Create a slider per track
+            var label = interfaceContext.getLabel(labelType, index, page.labelStart);
+            var sliderObj = new interfaceObject(audioObject, label);
+
+            sliderBox.appendChild(sliderObj.root);
+            audioObject.bindInterface(sliderObj);
+            interfaceContext.commentBoxes.createCommentBox(audioObject);
+            index += 1;
+        }
+    });
+    interfaceObj.options.forEach(function (option) {
+        if (option.type == "show") {
+            switch (option.name) {
+                case "playhead":
+                    var playbackHolder = document.getElementById('playback-holder');
+                    if (playbackHolder === null) {
+                        playbackHolder = document.createElement('div');
+                        playbackHolder.style.width = "100%";
+                        playbackHolder.align = 'center';
+                        playbackHolder.appendChild(interfaceContext.playhead.object);
+                        feedbackHolder.appendChild(playbackHolder);
+                    }
+                    break;
+                case "page-count":
+                    var pagecountHolder = document.getElementById('page-count');
+                    if (pagecountHolder === null) {
+                        pagecountHolder = document.createElement('div');
+                        pagecountHolder.id = 'page-count';
+                    }
+                    pagecountHolder.innerHTML = '<span>Page ' + (testState.stateIndex + 1) + ' of ' + testState.stateMap.length + '</span>';
+                    var inject = document.getElementById('interface-buttons');
+                    inject.appendChild(pagecountHolder);
+                    break;
+                case "volume":
+                    if (document.getElementById('master-volume-holder') === null) {
+                        feedbackHolder.appendChild(interfaceContext.volume.object);
+                    }
+                    break;
+                case "comments":
+                    interfaceContext.commentBoxes.showCommentBoxes(feedbackHolder, true);
+                    break;
+            }
+        }
+    });
+    resizeWindow();
+}
+
+function interfaceObject(audioObject, label) {
+    var container = document.getElementById("slider");
+    var playing = false;
+    var root = document.createElement("div");
+    root.className = "ordinal-element";
+    root.draggable = "true";
+    var labelElement = document.createElement("span");
+    labelElement.className = "ordinal-element-label";
+    labelElement.textContent = label;
+    root.appendChild(labelElement);
+    root.classList.add("disabled");
+    // 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( )
+    root.addEventListener("click", this, true);
+    root.addEventListener('dragstart', this, true);
+    root.addEventListener('dragenter', this, true);
+    root.addEventListener('dragover', this, true);
+    root.addEventListener('dragleave', this, true);
+    root.addEventListener('drop', this, true);
+    root.addEventListener('dragend', this, true);
+    this.handleEvent = function (event) {
+        if (event.type == "click") {
+            if (playing === false) {
+                audioEngineContext.play(audioObject.id);
+            } else {
+                audioEngineContext.stop();
+            }
+            playing = !playing;
+            return;
+        } else if (event.type == "dragstart") {
+            return dragStart.call(this, event);
+        } else if (event.type == "dragenter") {
+            return dragEnter.call(this, event);
+        } else if (event.type == "dragleave") {
+            return dragLeave.call(this, event);
+        } else if (event.type == "dragover") {
+            return dragOver.call(this, event);
+        } else if (event.type == "drop") {
+            return drop.call(this, event);
+        } else if (event.type == "dragend") {
+            return dragEnd.call(this, event);
+        }
+        throw (event);
+    };
+
+    function dragStart(e) {
+        e.currentTarget.classList.add("dragging");
+
+        e.dataTransfer.effectAllowed = 'move';
+        e.dataTransfer.setData('text/plain', audioObject.id);
+    }
+
+    function dragEnter(e) {
+        // this / e.target is the current hover target.
+        root.classList.add('over');
+    }
+
+    function dragLeave(e) {
+        root.classList.remove('over'); // this / e.target is previous target element.
+    }
+
+    function dragOver(e) {
+        if (e.preventDefault) {
+            e.preventDefault(); // Necessary. Allows us to drop.
+        }
+
+        e.dataTransfer.dropEffect = 'move'; // See the section on the DataTransfer object.
+
+        var srcid = Number(e.dataTransfer.getData("text/plain"));
+        var elements = container.childNodes;
+        var srcObject = audioEngineContext.audioObjects.find(function (ao) {
+            return ao.id === srcid;
+        });
+        var src = srcObject.interfaceDOM.root;
+        if (src !== root) {
+            var srcpos = srcObject.interfaceDOM.getElementPosition();
+            var mypos = this.getElementPosition();
+            var neighbour;
+            if (srcpos <= mypos) {
+                neighbour = root.nextElementSibling;
+            } else {
+                neighbour = root;
+            }
+            if (neighbour)
+                container.insertBefore(src, neighbour);
+            else {
+                container.removeChild(src);
+                container.appendChild(src);
+            }
+
+        }
+
+        return false;
+    }
+
+    function drop(e) {
+        // this / e.target is current target element.
+
+        if (e.stopPropagation) {
+            e.stopPropagation(); // stops the browser from redirecting.
+        }
+        if (e.preventDefault) {
+            e.preventDefault(); // Necessary. Allows us to drop.
+        }
+
+        audioEngineContext.audioObjects.forEach(function (ao) {
+            ao.interfaceDOM.processMovement();
+        });
+
+        return false;
+    }
+
+    function dragEnd(e) {
+        // this/e.target is the source node.
+        $(".ordinal-element").removeClass("dragging");
+        $(".ordinal-element").removeClass("over");
+    }
+
+    this.getElementPosition = function () {
+        var elements = container.childNodes,
+            position = 0,
+            elem = elements[0];
+        while (root !== elem) {
+            position++;
+            elem = elem.nextElementSibling;
+        }
+        return position;
+    };
+
+    this.processMovement = function () {
+        var time = audioEngineContext.timer.getTestTime();
+        var pos = this.getElementPosition();
+        var rank = pos / (audioEngineContext.audioObjects.length - 1);
+        audioObject.metric.moved(time, rank);
+        console.log('slider ' + audioObject.id + ' moved to ' + rank + ' (' + time + ')');
+    };
+
+    this.enable = function () {
+        // This is used to tell the interface object that playback of this node is ready
+        root.classList.remove("disabled");
+        labelElement.textContent = label;
+    };
+    this.updateLoading = function (progress) {
+        // progress is a value from 0 to 100 indicating the current download state of media files
+        labelElement.textContent = String(progress);
+    };
+    this.startPlayback = function () {
+        // Called when playback has begun
+        root.classList.add("playing");
+        if (audioObject.commentDOM) {
+            audioObject.commentDOM.trackComment.classList.add("comment-box-playing");
+        }
+    };
+    this.stopPlayback = function () {
+        // Called when playback has stopped. This gets called even if playback never started!
+        root.classList.remove("playing");
+        playing = false;
+        if (audioObject.commentDOM) {
+            audioObject.commentDOM.trackComment.classList.remove("comment-box-playing");
+        }
+    };
+    this.getValue = function () {
+        // Return the current value of the object. If there is no value, return 0
+        var pos = this.getElementPosition();
+        var rank = pos / (audioEngineContext.audioObjects.length - 1);
+    };
+    this.getPresentedId = function () {
+        // Return the presented ID of the object. For instance, the APE has sliders starting from 0. Whilst AB has alphabetical scale
+        return label;
+    };
+    this.canMove = function () {
+        // Return either true or false if the interface object can be moved. AB / Reference cannot, whilst sliders can and therefore have a continuous scale.
+        // These are checked primarily if the interface check option 'fragmentMoved' is enabled.
+        return true;
+    };
+    this.exportXMLDOM = function (audioObject) {
+        // Called by the audioObject holding this element to export the interface <value> node.
+        // If there is no value node (such as outside reference), return null
+        // If there are multiple value nodes (such as multiple scale / 2D scales), return an array of nodes with each value node having an 'interfaceName' attribute
+        // Use storage.document.createElement('value'); to generate the XML node.
+        var node = storage.document.createElement('value');
+        node.textContent = this.getValue();
+        return node;
+
+    };
+    this.error = function () {
+        // If there is an error with the audioObject, this will be called to indicate a failure
+        root.classList.remove("disabled");
+        labelElement.textContent = "Error";
+    };
+    Object.defineProperties(this, {
+        "root": {
+            "get": function () {
+                return root;
+            },
+            "set": function () {}
+        }
+    });
+}
+
+function resizeWindow(event) {
+    // Called on every window resize event, use this to scale your page properly
+    var w = $("#slider").width();
+    var N = audioEngineContext.audioObjects.length;
+    w /= N;
+    w -= 14;
+    w = Math.floor(w);
+    $(".ordinal-element").width(w);
+}
+
+function buttonSubmitClick() // TODO: Only when all songs have been played!
+{
+    var checks = testState.currentStateMap.interfaces[0].options,
+        canContinue = true;
+
+    // Check that the anchor and reference objects are correctly placed
+    if (interfaceContext.checkHiddenAnchor() === false) {
+        return;
+    }
+    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
+                    checkState = interfaceContext.checkAllPlayed(checks[i].errorMessage);
+                    break;
+                case 'fragmentFullPlayback':
+                    // Check all fragments have been played to their full length
+                    checkState = interfaceContext.checkAllPlayed(checks[i].errorMessage);
+                    console.log('NOTE: fragmentFullPlayback not currently implemented, performing check fragmentPlayed instead');
+                    break;
+                case 'fragmentMoved':
+                    // Check all fragment sliders have been moved.
+                    checkState = interfaceContext.checkAllMoved(checks[i].errorMessage);
+                    break;
+                case 'fragmentComments':
+                    // Check all fragment sliders have been moved.
+                    checkState = interfaceContext.checkAllCommented(checks[i].errorMessage);
+                    break;
+                case 'scalerange':
+                    // Check the scale has been used effectively
+                    checkState = interfaceContext.checkScaleRange(checks[i].errorMessage);
+
+                    break;
+                default:
+                    console.log("WARNING - Check option " + checks[i].check + " is not supported on this interface");
+                    break;
+            }
+        }
+        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) {
+                interfaceContext.lightbox.post("Warning", 'You have not started the test! Please press start to begin the test!');
+                return;
+            }
+        }
+        testState.advanceState();
+    }
+}
+
+function pageXMLSave(store, pageSpecification) {
+    // MANDATORY
+    // Saves a specific test page
+    // You can use this space to add any extra nodes to your XML <audioHolder> saves
+    // Get the current <page> information in store (remember to appendChild your data to it)
+    // pageSpecification is the current page node configuration
+    // To create new XML nodes, use storage.document.createElement();
+}
--- a/interfaces/timeline.js	Tue Jun 27 21:22:15 2017 +0100
+++ b/interfaces/timeline.js	Wed Jul 05 12:22:29 2017 +0100
@@ -441,9 +441,15 @@
     };
     this.startPlayback = function () {
         // Called when playback has begun
-        canvasIntervalID = window.setInterval(this.canvas.drawTicker.bind(this.canvas), 100);
+        var animate = function () {
+            this.canvas.drawTicker.call(this.canvas);
+            if (this.playButton.DOM.textContent == "Stop") {
+                window.requestAnimationFrame(animate);
+            }
+        }.bind(this);
         this.playButton.DOM.textContent = "Stop";
         interfaceContext.commentBoxes.highlightById(audioObject.id);
+        canvasIntervalID = window.requestAnimationFrame(animate);
     };
     this.stopPlayback = function () {
         // Called when playback has stopped. This gets called even if playback never started!
--- a/js/core.js	Tue Jun 27 21:22:15 2017 +0100
+++ b/js/core.js	Wed Jul 05 12:22:29 2017 +0100
@@ -3355,6 +3355,25 @@
     };
 
 
+    this.sortFragmentsByScore = function () {
+        var elements = audioEngineContext.audioObjects.filter(function (elem) {
+            return elem.specification.type !== "outside-reference";
+        });
+        var indexes = [];
+        var i = 0;
+        while (indexes.push(i++) < elements.length);
+        return indexes.sort(function (x, y) {
+            var a = elements[x].interfaceDOM.getValue();
+            var b = elements[y].interfaceDOM.getValue();
+            if (a > b) {
+                return 1;
+            } else if (a < b) {
+                return -1;
+            }
+            return 0;
+        }, elements[0].interfaceDOM.getValue());
+    };
+
     this.storeErrorNode = function (errorMessage) {
         var time = audioEngineContext.timer.getTestTime();
         var node = storage.document.createElement('error');
--- a/js/specification.js	Tue Jun 27 21:22:15 2017 +0100
+++ b/js/specification.js	Wed Jul 05 12:22:29 2017 +0100
@@ -21,19 +21,28 @@
     this.maxNumberPlays = undefined;
 
     // nodes
-    this.metrics = undefined;
-    this.preTest = undefined;
-    this.postTest = undefined;
+    this.metrics = new metricNode();
+    this.preTest = new surveyNode(this);
+    this.postTest = new surveyNode(this);
+    this.preTest.location = "pre";
+    this.postTest.location = "post";
     this.pages = [];
-    this.interfaces = undefined;
+    this.interfaces = new interfaceNode(this);
     this.errors = [];
     this.exitText = "Thank you.";
 
+    // Creators
+    this.createNewPage = function () {
+        var newpage = new page(this);
+        this.pages.push(newpage);
+        return newpage;
+    };
+
     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') === null && schema.getAttribute('ref') !== undefined) {
-            schema = schemaRoot.getAllElementsByName(schema.getAttribute('ref'))[0];
+            schema = schemaRoot.querySelector("[name=" + schema.getAttribute('ref') + "]");
         }
         var defaultOpt = schema.getAttribute('default');
         if (attribute === null) {
@@ -43,7 +52,7 @@
         if (typeof dataType == "string") {
             dataType = dataType.substr(3);
         } else {
-            var rest = schema.getAllElementsByTagName("xs:restriction").concat(schema.getAllElementsByTagName("xs:enumeration"));
+            var rest = schema.querySelectorAll("restriction,enumeration");
             if (rest.length > 0) {
                 dataType = rest[0].getAttribute("base");
                 if (typeof dataType == "string") {
@@ -110,9 +119,9 @@
         // projectXML - DOM Parsed document
         this.projectXML = projectXML.childNodes[0];
         var setupNode = projectXML.getElementsByTagName('setup')[0];
-        var schemaSetup = schemaRoot.getAllElementsByName('setup')[0];
+        var schemaSetup = schemaRoot.querySelector('[name=setup]');
         // First decode the attributes
-        var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
+        var attributes = schemaSetup.querySelectorAll('attribute');
         var i;
         for (i = 0; i < attributes.length; i++) {
             var attributeName = attributes[i].getAttribute('name') || attributes[i].getAttribute('ref');
@@ -129,8 +138,6 @@
             this.exitText = exitTextNode[0].textContent;
         }
 
-        this.metrics = new this.metricNode();
-
         this.metrics.decode(this, setupNode.getElementsByTagName('metric')[0]);
 
         // Now process the survey node options
@@ -140,12 +147,10 @@
             switch (location) {
                 case 'pre':
                 case 'before':
-                    this.preTest = new this.surveyNode(this);
                     this.preTest.decode(this, survey[i]);
                     break;
                 case 'post':
                 case 'after':
-                    this.postTest = new this.surveyNode(this);
                     this.postTest.decode(this, survey[i]);
                     break;
             }
@@ -155,17 +160,17 @@
         if (interfaceNode.length > 1) {
             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) {
             interfaceNode = interfaceNode[0];
-            this.interfaces.decode(this, interfaceNode, this.schema.getAllElementsByName('interface')[1]);
+            this.interfaces.decode(this, interfaceNode, this.schema.querySelectorAll('[name=interface]')[1]);
         }
 
         // Page tags
         var pageTags = projectXML.getElementsByTagName('page');
-        var pageSchema = this.schema.getAllElementsByName('page')[0];
+        var pageSchema = this.schema.querySelector('[name=page]');
         for (i = 0; i < pageTags.length; i++) {
-            var node = new this.page(this);
+            var node = new page(this);
             node.decode(this, pageTags[i], pageSchema);
             this.pages.push(node);
         }
@@ -178,9 +183,9 @@
         root.setAttribute("xsi:noNamespaceSchemaLocation", "test-schema.xsd");
         // Build setup node
         var setup = RootDocument.createElement("setup");
-        var schemaSetup = schemaRoot.getAllElementsByName('setup')[0];
+        var schemaSetup = schemaRoot.querySelector('[name=setup]');
         // First decode the attributes
-        var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
+        var attributes = schemaSetup.querySelectorAll('attribute');
         for (var i = 0; i < attributes.length; i++) {
             var name = attributes[i].getAttribute("name");
             if (name === undefined) {
@@ -207,13 +212,18 @@
         return RootDocument;
     };
 
-    this.surveyNode = function (specification) {
+    function surveyNode(specification) {
         this.location = undefined;
         this.options = [];
         this.parent = undefined;
-        this.schema = schemaRoot.getAllElementsByName('survey')[0];
         this.specification = specification;
 
+        this.addOption = function () {
+            var node = new this.OptionNode(this.specification);
+            this.options.push(node);
+            return node;
+        };
+
         this.OptionNode = function (specification) {
             this.type = undefined;
             this.schema = undefined;
@@ -230,8 +240,8 @@
             this.conditions = [];
 
             this.decode = function (parent, child) {
-                this.schema = schemaRoot.getAllElementsByName(child.nodeName)[0];
-                var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
+                this.schema = schemaRoot.querySelector("[name=" + child.nodeName + "]");
+                var attributeMap = this.schema.querySelectorAll('attribute');
                 var i;
                 for (i in attributeMap) {
                     if (isNaN(Number(i)) === true) {
@@ -385,6 +395,7 @@
             };
         };
         this.decode = function (parent, xml) {
+            this.schema = schemaRoot.querySelector('[name=survey]');
             this.parent = parent;
             this.location = xml.getAttribute('location');
             if (this.location == 'before') {
@@ -412,17 +423,18 @@
             }
             return node;
         };
-    };
+    }
 
-    this.interfaceNode = function (specification) {
+    function interfaceNode(specification) {
         this.title = undefined;
         this.name = undefined;
         this.image = undefined;
         this.options = [];
         this.scales = [];
-        this.schema = schemaRoot.getAllElementsByName('interface')[1];
+        this.schema = undefined;
 
         this.decode = function (parent, xml) {
+            this.schema = schemaRoot.querySelectorAll('[name=interface]')[1];
             this.name = xml.getAttribute('name');
             var titleNode = xml.getElementsByTagName('title');
             if (titleNode.length == 1) {
@@ -430,8 +442,8 @@
             }
             var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
             // Extract interfaceoption node schema
-            var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
-            var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
+            var interfaceOptionNodeSchema = this.schema.querySelector('[name=interfaceoption]');
+            var attributeMap = interfaceOptionNodeSchema.querySelectorAll('attribute');
             var i, j;
             for (i = 0; i < interfaceOptionNodes.length; i++) {
                 var ioNode = interfaceOptionNodes[i];
@@ -458,7 +470,7 @@
             var scaleParent = xml.getElementsByTagName('scales');
             if (scaleParent.length == 1) {
                 scaleParent = scaleParent[0];
-                var scalelabels = scaleParent.getAllElementsByTagName('scalelabel');
+                var scalelabels = scaleParent.querySelectorAll('scalelabel');
                 for (i = 0; i < scalelabels.length; i++) {
                     this.scales.push({
                         text: scalelabels[i].textContent,
@@ -505,9 +517,9 @@
             }
             return node;
         };
-    };
+    }
 
-    this.metricNode = function () {
+    function metricNode() {
         this.enabled = [];
         this.decode = function (parent, xml) {
             var children = xml.getElementsByTagName('metricenable');
@@ -530,9 +542,9 @@
             }
             return node;
         };
-    };
+    }
 
-    this.page = function (specification) {
+    function page(specification) {
         this.presentedId = undefined;
         this.id = undefined;
         this.title = undefined;
@@ -543,8 +555,10 @@
         this.loudness = undefined;
         this.label = undefined;
         this.labelStart = undefined;
-        this.preTest = undefined;
-        this.postTest = undefined;
+        this.preTest = new surveyNode(specification);
+        this.postTest = new surveyNode(specification);
+        this.preTest.location = "pre";
+        this.postTest.location = "post";
         this.interfaces = [];
         this.playOne = undefined;
         this.restrictMovement = undefined;
@@ -554,13 +568,29 @@
         this.maxNumberPlays = undefined;
         this.audioElements = [];
         this.commentQuestions = [];
-        this.schema = schemaRoot.getAllElementsByName("page")[0];
+        this.schema = schemaRoot.querySelector("[name=page]");
         this.specification = specification;
         this.parent = undefined;
 
+        this.addInterface = function () {
+            var node = new interfaceNode(specification);
+            this.interfaces.push(node);
+            return node;
+        };
+        this.addCommentQuestion = function () {
+            var node = new commentQuestionNode(specification);
+            this.commentQuestions.push(node);
+            return node;
+        };
+        this.addAudioElement = function () {
+            var node = new audioElementNode(specification);
+            this.audioElements.push(node);
+            return node;
+        };
+
         this.decode = function (parent, xml) {
             this.parent = parent;
-            var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
+            var attributeMap = this.schema.querySelectorAll('attribute');
             var i, node;
             for (i = 0; i < attributeMap.length; i++) {
                 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
@@ -584,30 +614,28 @@
             }
 
             // Now decode the interfaces
-            var interfaceNode = xml.getElementsByTagName('interface');
-            for (i = 0; i < interfaceNode.length; i++) {
-                node = new parent.interfaceNode(this.specification);
-                node.decode(this, interfaceNode[i], parent.schema.getAllElementsByName('interface')[1]);
+            var interfaceNodes = xml.getElementsByTagName('interface');
+            for (i = 0; i < interfaceNodes.length; i++) {
+                node = new interfaceNode(this.specification);
+                node.decode(this, interfaceNodes[i], parent.schema.querySelectorAll('[name=interface]')[1]);
                 this.interfaces.push(node);
             }
 
             // Now process the survey node options
             var survey = xml.getElementsByTagName('survey');
-            var surveySchema = parent.schema.getAllElementsByName('survey')[0];
+            var surveySchema = parent.schema.querySelector('[name=survey]');
             for (i = 0; i < survey.length; i++) {
                 var location = survey[i].getAttribute('location');
                 if (location == 'pre' || location == 'before') {
-                    if (this.preTest !== undefined) {
+                    if (this.preTest.options.length !== 0) {
                         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 !== undefined) {
+                    if (this.postTest.options.length !== 0) {
                         this.errors.push("Already a post/after test survey defined! Ignoring second!!");
                     } else {
-                        this.postTest = new parent.surveyNode(this.specification);
                         this.postTest.decode(parent, survey[i], surveySchema);
                     }
                 }
@@ -616,7 +644,7 @@
             // Now process the audioelement tags
             var audioElements = xml.getElementsByTagName('audioelement');
             for (i = 0; i < audioElements.length; i++) {
-                var audioNode = new this.audioElementNode(this.specification);
+                var audioNode = new audioElementNode(this.specification);
                 audioNode.decode(this, audioElements[i]);
                 this.audioElements.push(audioNode);
             }
@@ -627,7 +655,7 @@
                 cqNode = cqNode[0];
                 var commentQuestion = cqNode.firstElementChild;
                 while (commentQuestion) {
-                    node = new this.commentQuestionNode(this.specification);
+                    node = new commentQuestionNode(this.specification);
                     node.decode(parent, commentQuestion);
                     this.commentQuestions.push(node);
                     commentQuestion = commentQuestion.nextElementSibling;
@@ -638,7 +666,7 @@
         this.encode = function (root) {
             var AHNode = root.createElement("page");
             // First decode the attributes
-            var attributes = this.schema.getAllElementsByTagName('xs:attribute');
+            var attributes = this.schema.querySelectorAll('attribute');
             var i;
             for (i = 0; i < attributes.length; i++) {
                 var name = attributes[i].getAttribute("name");
@@ -671,12 +699,12 @@
             return AHNode;
         };
 
-        this.commentQuestionNode = function (specification) {
+        function commentQuestionNode(specification) {
             this.id = undefined;
             this.name = undefined;
             this.type = undefined;
             this.statement = undefined;
-            this.schema = schemaRoot.getAllElementsByName('commentquestion')[0];
+            this.schema = schemaRoot.querySelector('[name=commentquestion]');
             this.decode = function (parent, xml) {
                 this.id = xml.id;
                 this.name = xml.getAttribute('name');
@@ -797,9 +825,9 @@
                 }
                 return node;
             };
-        };
+        }
 
-        this.audioElementNode = function (specification) {
+        function audioElementNode(specification) {
             this.url = undefined;
             this.id = undefined;
             this.name = undefined;
@@ -816,11 +844,11 @@
             this.minNumberPlays = undefined;
             this.maxNumberPlays = undefined;
             this.alternatives = [];
-            this.schema = schemaRoot.getAllElementsByName('audioelement')[0];
+            this.schema = schemaRoot.querySelector('[name=audioelement]');
             this.parent = undefined;
             this.decode = function (parent, xml) {
                 this.parent = parent;
-                var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
+                var attributeMap = this.schema.querySelectorAll('attribute');
                 for (var i = 0; i < attributeMap.length; i++) {
                     var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
                     var projectAttr = xml.getAttribute(attributeName);
@@ -844,7 +872,7 @@
             };
             this.encode = function (root) {
                 var AENode = root.createElement("audioelement");
-                var attributes = this.schema.getAllElementsByTagName('xs:attribute');
+                var attributes = this.schema.querySelectorAll('attribute');
                 for (var i = 0; i < attributes.length; i++) {
                     var name = attributes[i].getAttribute("name");
                     if (name === null) {
@@ -862,6 +890,6 @@
                 });
                 return AENode;
             };
-        };
-    };
+        }
+    }
 }
--- a/test.html	Tue Jun 27 21:22:15 2017 +0100
+++ b/test.html	Wed Jul 05 12:22:29 2017 +0100
@@ -37,6 +37,7 @@
         <button id="popup-previous" class="popupButton">Back</button>
     </div>
     <div class="testHalt" style="visibility: hidden"></div>
+    <div id="footer"><a target="_blank" href="https://github.com/BrechtDeMan/WebAudioEvaluationTool">Web Audio Evaluation Toolbox (v1.2.1)</a></div>
 </body>
 
 </html>
--- a/test_create.html	Tue Jun 27 21:22:15 2017 +0100
+++ b/test_create.html	Wed Jul 05 12:22:29 2017 +0100
@@ -1,31 +1,1086 @@
-<html>
-
-<head>
-    <meta http-equiv="content-type" content="text/html; charset=utf-8">
-    <!-- This defines the test creator tool for the Web Audio Evaluation Toolbox -->
-    <link rel='stylesheet' type="text/css" href="test_create/style.css" />
-    <link rel='stylesheet' type="text/css" href="test_create/custom.css" />
-    <script type="text/javascript">
-        window.onbeforeunload = function(e) {
-            var message = 'If you leave the page now, any unsaved changes will be lost',
-                e = e || window.event;
-            if (e) {
-                e.returnValue = message;
-            }
-            return message;
-        };
-        // Copy of Specifiation node from Core.js
-
-    </script>
-    <script src="js/jquery-2.1.4.js"></script>
-    <script type="text/javascript" src='js/specification.js'></script>
-    <script type="text/javascript" src="test_create/test_core.js"></script>
-</head>
-
-<body>
-    <div id="popupHolder"></div>
-    <div id="blanket"></div>
-    <div id="content"></div>
-</body>
-
-</html>
+<html>
+
+<head>
+    <meta http-equiv="content-type" content="text/html; charset=utf-8">
+    <!-- This defines the test creator tool for the Web Audio Evaluation Toolbox -->
+    <link rel="stylesheet" type="text/css" href="test_create/style.css" />
+    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+    <script src="js/jquery-2.1.4.js"></script>
+    <script src="js/angular.min.js"></script>
+    <script type="text/javascript" src="js/specification.js"></script>
+    <script type="text/javascript" src="test_create/test_core.js"></script>
+    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
+    <script type="text/javascript" src="js/xmllint.js"></script>
+
+    <title>WAET 1.2.1 Test Creator</title>
+</head>
+
+<body ng-app="creator" ng-controller="view">
+    <div class="container">
+        <div id="pageRoot">
+            <h1>Web Audio Evaluation Tool - Test Creator</h1>
+        </div>
+        <button type="button" class="btn btn-info" ng-click="validate()">Validate</button>
+        <button type="button" class="btn btn-success" ng-click="exportXML()" ng-disabled="validated == false">Export XML</button>
+        <div ng-switch on="validated" ng-show="showValidationMessages">
+            <div class="panel panel-danger" ng-switch-when="false">
+                <div class="panel-heading">
+                    <button type="button" class="close" data-dismiss="alert" aria-label="Close" ng-click="hideValidationMessages"><span aria-hidden="true">&times;</span></button>
+                    <h3 class="panel-title">Invalid Specification!</h3>
+                </div>
+                <div class="panel-body">
+                    <p>Your specification is invalid. Please fix the following issues!</p>
+                    <ul id="validation-error-list">
+                        <li>Errors</li>
+                    </ul>
+                </div>
+            </div>
+            <div class="alert alert-success" role="alert" ng-switch-when="true">
+                <button type="button" class="close" data-dismiss="alert" aria-label="Close" ng-click="hideValidationMessages"><span aria-hidden="true">&times;</span></button>
+                <strong>Validates!</strong><span>Well done, you can export this specification!</span>
+            </div>
+        </div>
+        <div id="setupNode" class="node" ng-controller="setup">
+            <h2>Setup</h2>
+            <div class="attributes">
+                <div class="attribute">
+                    <span>Interface: </span>
+                    <input type="text" ng-model="specification.interface" required/>
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="If you would like to save to a server other than your hosting server, you can place the full WAET URL here">
+                    <span>Save URL: </span>
+                    <input type="text" ng-model="specification.projectReturn" placeholder="save.php" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Once the test is completed and save confirmed, the browser will redirect to this page, if not defined.">
+                    <span>Exit URL: </span>
+                    <input type="text" ng-model="specification.returnURL" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Randomise the page order">
+                    <span>Randomise Page Order: </span>
+                    <input type="checkbox" ng-model="specification.randomiseOrder" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Set the number of pages to present to the user. This includes repeated pages. Set to '0' or blank to ignore. Randomise page order must be selected.">
+                    <span>Page Pool Size: </span>
+                    <input type="number" ng-model="specification.poolSize" min="0" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Automatically analyse and normalsie audio to this target LUFS. If unsure, use -25LUFS. 0 or blank disables normalisation">
+                    <span>Loudness Normalisation (LUFS): </span>
+                    <input type="number" ng-model="specification.loudness" max="0" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Only perform the test if the browser reported sampling rate matches this.">
+                    <span>Fixed Sampling Rate: </span>
+                    <input type="number" ng-model="specification.sampleRate" min="0" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Show a 'method of adjustment' audio calibration before testing.">
+                    <span>Pre-Test audio calibration: </span>
+                    <input type="checkbox" ng-model="specification.calibration" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Default cross-fade time when switching between elements. Can be over-ridden on each page">
+                    <span>Global Cross-fade time: </span>
+                    <input type="number" ng-model="specification.crossFade" min="0" step="0.1" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Default pre-play element silence. Can be over-ridden on each page and element">
+                    <span>Global Fragment Pre-Silence: </span>
+                    <input type="number" ng-model="specification.preSilence" min="0" step="0.1" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Default post-play element silence. Can be over-ridden on each page and element">
+                    <span>Global Fragment Post-Silence: </span>
+                    <input type="number" ng-model="specification.preSilence" min="0" step="0.1" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Disable switching of audio elements">
+                    <span>Play audio one-at-a-time: </span>
+                    <input type="checkbox" ng-model="specification.playOne" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Minimum number of times an audio fragment must be played">
+                    <span>Minimum number of fragment plays</span>
+                    <input type="number" ng-model="specification.minNumberPlays" min="0" max="{{specification.maxNumberPlays}}" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Maximum number of times an audio fragment can be played">
+                    <span>Maximum number of fragment plays</span>
+                    <input type="number" ng-model="specification.maxNumberPlays" min="{{specification.minNumberPlays || 0}}" />
+                </div>
+            </div>
+            <div class="node">
+                <h2>Test Completed Message</h2>
+                <textarea ng-model="specification.exitText" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Once the test is completed, you can show a message to the user. Markdown syntax is supported for formatting."></textarea>
+            </div>
+            <div id="metricsNode" class="node">
+                <h3>Session Metrics</h3>
+                <div class="attributes">
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Report the total test participation time">
+                        <span>Collect Total Test Time: </span>
+                        <input type="checkbox" value="testTimer" ng-click="enableMetric($event)" />
+                    </div>
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Collect the accumulative listening time for each fragment">
+                        <span>Collect Fragment Listen Time: </span>
+                        <input type="checkbox" value="elementTimer" ng-click="enableMetric($event)" />
+                    </div>
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Store the initial position of the fragment">
+                        <span>Collect Fragment Initial Position: </span>
+                        <input type="checkbox" value="elementInitialPosition" ng-click="enableMetric($event)" />
+                    </div>
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Store each movement / value change of each fragment with page-relative timestamps.">
+                        <span>Collect Fragment Movements: </span>
+                        <input type="checkbox" value="elementTracker" ng-click="enableMetric($event)" />
+                    </div>
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Store boolean reporting if a fragment has been played">
+                        <span>Collect Fragment Listened To Flag: </span>
+                        <input type="checkbox" value="elementFlagListenedTo" ng-click="enableMetric($event)" />
+                    </div>
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Store boolean reporting if a fragment has been moved">
+                        <span>Collect Fragment Moved Flag: </span>
+                        <input type="checkbox" value="elementFlagMoved" ng-click="enableMetric($event)" />
+                    </div>
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Store each time a fragment starts and stops playback with page relative timestamps. Also holds fragment relative timestamps.">
+                        <span>Collect Fragment Listened Flag: </span>
+                        <input type="checkbox" value="elementListenTracker" ng-click="enableMetric($event)" />
+                    </div>
+                </div>
+            </div>
+            <div id="globalpresurvey" class="node" ng-controller="survey" ng-init="survey = specification.preTest">
+                <h2>Pre Test Survey</h2>
+                <button type="button" class="btn btn-success" ng-click="addSurveyEntry()">Add Entry</button>
+                <div class="node" ng-repeat="opt in survey.options" ng-controller="surveyOption">
+                    <h3>Survey Entry</h3>
+                    <button type="button" class="btn btn-danger" ng-click="removeSurveyEntry(opt);">Delete Entry</button>
+                    <div class="attributes">
+                        <div class="attribute">
+                            <span>Survey Type: </span>
+                            <select ng-model="opt.type">
+                                <option value="question">Question</option>
+                                <option value="radio">Radio</option>
+                                <option value="checkbox">Checkbox</option>
+                                <option value="statement">Statement</option>
+                                <option value="number">Number</option>
+                                <option value="slider">Slider</option>
+                                <option value="video">Video</option>
+                                <option value="youtube">YouTube</option>
+                            </select>
+                        </div>
+                        <div class="attribute">
+                            <span>Unique Survey Entry ID:</span>
+                            <input type="text" ng-model="opt.id" required/>
+                        </div>
+                        <div class="attribute">
+                            <span>Entry Name:</span>
+                            <input type="text" ng-model="opt.name" />
+                        </div>
+                        <div class="attribute" ng-show="['question', 'checkbox', 'radio', 'number'].indexOf(opt.type) >= 0">
+                            <span>Mandatory:</span>
+                            <input type="checkbox" ng-model="opt.mandatory" />
+                        </div>
+                        <div class="attribute">
+                            <span>Minimum Wait Time (s):</span>
+                            <input type="number" ng-model="opt.minWait" min="0" />
+                        </div>
+                        <div class="attribute" ng-show="opt.type == 'question'">
+                            <span>Box Size:</span>
+                            <select ng-model="opt.boxsize">
+                                <option value="small">Small</option>
+                                <option value="normal">Normal</option>
+                                <option value="large">Large</option>
+                                <option value="huge">Huge</option>
+                            </select>
+                        </div>
+                        <div class="attribute" ng-show="['checkbox', 'radio'].indexOf(opt.type) >= 0">
+                            <span>Minimum Selected:</span>
+                            <input type="number" ng-model="opt.min" min="0" />
+                        </div>
+                        <div class="attribute" ng-show="['checkbox', 'radio'].indexOf(opt.type) >= 0">
+                            <span>Maximum Selected:</span>
+                            <input type="number" ng-model="opt.max" max="{{opt.options.length}}" />
+                        </div>
+                        <div class="attribute" ng-show="['slider', 'number'].indexOf(opt.type) >= 0">
+                            <span>Minimum Value:</span>
+                            <input type="number" ng-model="opt.min" />
+                        </div>
+                        <div class="attribute" ng-show="['slider', 'number'].indexOf(opt.type) >= 0">
+                            <span>Maximum Value:</span>
+                            <input type="number" ng-model="opt.max" />
+                        </div>
+                        <div class="attribute" ng-show="['video', 'youtube'].indexOf(opt.type) >= 0">
+                            <span>Video URL:</span>
+                            <input type="text" ng-model="opt.url" />
+                        </div>
+                    </div>
+                    <div class="node">
+                        <h4>Statement</h4>
+                        <textarea ng-model="opt.statement"></textarea>
+                    </div>
+                    <div class="node" ng-show="['checkbox', 'radio'].indexOf(opt.type) >= 0">
+                        <h4>Options</h4>
+                        <div>
+                            <button type="button" class="btn btn-default" ng-click="addOption();">Add Option</button>
+                        </div>
+                        <div class="node" ng-repeat="option in opt.options">
+                            <div class="attributes">
+                                <div class="attribute">
+                                    <button type="button" class="btn btn-default" ng-click="removeOption(option);">Remove</button>
+                                </div>
+                                <div class="attribute">
+                                    <span>Name: </span>
+                                    <input type="text" ng-model="option.name" required/>
+                                </div>
+                                <div class="attribute">
+                                    <span>Displayed Text: </span>
+                                    <input type="text" ng-model="option.text" required/>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="node">
+                        <h4>Conditionals</h4>
+                        <button type="button" class="btn btn-default" ng-click="addCondition()">Add Condition</button>
+                        <div class="node" ng-repeat="condition in opt.conditions">
+                            <div class="attributes">
+                                <div class="attribute">
+                                    <button type="button" class="btn btn-danger" ng-click="removeCondition(condition)">Remove</button>
+                                </div>
+                                <div class="attribute">
+                                    <span>Check Type:</span>
+                                    <select ng-model="condition.check">
+                                        <option value="equals">Equal To</option>
+                                        <option value="lessThan">Less Than</option>
+                                        <option value="greaterThan">Greater Than</option>
+                                        <option value="stringContains">String Contains</option>
+                                    </select>
+                                </div>
+                                <div class="attribute">
+                                    <span>Value: </span>
+                                    <input type="text" ng-model="condition.value" required/>
+                                </div>
+                                <div class="attribute">
+                                    <span>Jump To On Pass: </span>
+                                    <select ng-model="condition.jumpToOnPass">
+                                        <option value="">None</option>
+                                        <option ng-repeat="entry in survey.options" value="{{entry.id}}">{{entry.id}}</option>
+                                    </select>
+                                </div>
+                                <div class="attribute">
+                                    <span>Jump To On Fail: </span>
+                                    <select ng-model="condition.jumpToOnFail">
+                                        <option value="">None</option>
+                                        <option ng-repeat="entry in survey.options" value="{{entry.id}}">{{entry.id}}</option>
+                                    </select>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div id="globalpostsurvey" class="node" ng-controller="survey" ng-init="survey = specification.postTest">
+                <h2>Post Test Survey</h2>
+                <button type="button" class="btn btn-success" ng-click="addSurveyEntry()">Add Entry</button>
+                <div class="node" ng-repeat="opt in survey.options" ng-controller="surveyOption">
+                    <h3>Survey Entry</h3>
+                    <button type="button" class="btn btn-danger" ng-click="removeSurveyEntry(opt);">Delete Entry</button>
+                    <div class="attributes">
+                        <div class="attribute">
+                            <span>Survey Type: </span>
+                            <select ng-model="opt.type">
+                                <option value="question">Question</option>
+                                <option value="radio">Radio</option>
+                                <option value="checkbox">Checkbox</option>
+                                <option value="statement">Statement</option>
+                                <option value="number">Number</option>
+                                <option value="slider">Slider</option>
+                                <option value="video">Video</option>
+                                <option value="youtube">YouTube</option>
+                            </select>
+                        </div>
+                        <div class="attribute">
+                            <span>Unique Survey Entry ID:</span>
+                            <input type="text" ng-model="opt.id" required/>
+                        </div>
+                        <div class="attribute">
+                            <span>Entry Name:</span>
+                            <input type="text" ng-model="opt.name" />
+                        </div>
+                        <div class="attribute" ng-show="['question', 'checkbox', 'radio', 'number'].indexOf(opt.type) >= 0">
+                            <span>Mandatory:</span>
+                            <input type="checkbox" ng-model="opt.mandatory" />
+                        </div>
+                        <div class="attribute">
+                            <span>Minimum Wait Time (s):</span>
+                            <input type="number" ng-model="opt.minWait" min="0" />
+                        </div>
+                        <div class="attribute" ng-show="opt.type == 'question'">
+                            <span>Box Size:</span>
+                            <select ng-model="opt.boxsize">
+                                <option value="small">Small</option>
+                                <option value="normal">Normal</option>
+                                <option value="large">Large</option>
+                                <option value="huge">Huge</option>
+                            </select>
+                        </div>
+                        <div class="attribute" ng-show="['checkbox', 'radio'].indexOf(opt.type) >= 0">
+                            <span>Minimum Selected:</span>
+                            <input type="number" ng-model="opt.min" min="0" />
+                        </div>
+                        <div class="attribute" ng-show="['checkbox', 'radio'].indexOf(opt.type) >= 0">
+                            <span>Maximum Selected:</span>
+                            <input type="number" ng-model="opt.max" max="{{opt.options.length}}" />
+                        </div>
+                        <div class="attribute" ng-show="['slider', 'number'].indexOf(opt.type) >= 0">
+                            <span>Minimum Value:</span>
+                            <input type="number" ng-model="opt.min" />
+                        </div>
+                        <div class="attribute" ng-show="['slider', 'number'].indexOf(opt.type) >= 0">
+                            <span>Maximum Value:</span>
+                            <input type="number" ng-model="opt.max" />
+                        </div>
+                        <div class="attribute" ng-show="['video', 'youtube'].indexOf(opt.type) >= 0">
+                            <span>Video URL:</span>
+                            <input type="text" ng-model="opt.url" />
+                        </div>
+                    </div>
+                    <div class="node">
+                        <h4>Statement</h4>
+                        <textarea ng-model="opt.statement"></textarea>
+                    </div>
+                    <div class="node" ng-show="['checkbox', 'radio'].indexOf(opt.type) >= 0">
+                        <h4>Options</h4>
+                        <div>
+                            <button type="button" class="btn btn-default" ng-click="addOption();">Add Option</button>
+                        </div>
+                        <div class="node" ng-repeat="option in opt.options">
+                            <div class="attributes">
+                                <div class="attribute">
+                                    <button type="button" class="btn btn-default" ng-click="removeOption(option);">Remove</button>
+                                </div>
+                                <div class="attribute">
+                                    <span>Name: </span>
+                                    <input type="text" ng-model="option.name" required/>
+                                </div>
+                                <div class="attribute">
+                                    <span>Displayed Text: </span>
+                                    <input type="text" ng-model="option.text" required/>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="node">
+                        <h4>Conditionals</h4>
+                        <button type="button" class="btn btn-default" ng-click="addCondition()">Add Condition</button>
+                        <div class="node" ng-repeat="condition in opt.conditions">
+                            <div class="attributes">
+                                <div class="attribute">
+                                    <button type="button" class="btn btn-danger" ng-click="removeCondition(condition)">Remove</button>
+                                </div>
+                                <div class="attribute">
+                                    <span>Check Type:</span>
+                                    <select ng-model="condition.check">
+                                        <option value="equals">Equal To</option>
+                                        <option value="lessThan">Less Than</option>
+                                        <option value="greaterThan">Greater Than</option>
+                                        <option value="stringContains">String Contains</option>
+                                    </select>
+                                </div>
+                                <div class="attribute">
+                                    <span>Value: </span>
+                                    <input type="text" ng-model="condition.value" required/>
+                                </div>
+                                <div class="attribute">
+                                    <span>Jump To On Pass: </span>
+                                    <select ng-model="condition.jumpToOnPass">
+                                        <option value="">None</option>
+                                        <option ng-repeat="entry in survey.options" value="{{entry.id}}">{{entry.id}}</option>
+                                    </select>
+                                </div>
+                                <div class="attribute">
+                                    <span>Jump To On Fail: </span>
+                                    <select ng-model="condition.jumpToOnFail">
+                                        <option value="">None</option>
+                                        <option ng-repeat="entry in survey.options" value="{{entry.id}}">{{entry.id}}</option>
+                                    </select>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div id="globalinterface" class="node" ng-controller="interfaceNode" ng-init="interface = specification.interfaces">
+                <h2>Interface (Globals)</h2>
+                <div class="node interfaceOptions">
+                    <div class="attributes">
+                        <div class="attribute" name="fragmentPlayed" type="check">
+                            <span>Check all fragments played: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                        <div class="attribute" name="fragmentFullPlayback" type="check">
+                            <span>Check all fragments fully played: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                        <div class="attribute" name="fragmentMoved" type="check">
+                            <span>Check all fragments have been moved: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                        <div class="attribute" name="fragmentComments" type="check">
+                            <span>Check all fragments have comments: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                        <div class="attribute" name="scalerange" type="check">
+                            <span>Enforce a scale usage: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                            <span>Minimum:</span>
+                            <input type="number" min="0" max="100" name="min" />
+                            <span>Maximum:</span>
+                            <input type="number" min="0" max="100" name="max" />
+                        </div>
+                        <div class="attribute" name="volume" type="show">
+                            <span>Show master volume control: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                        <div class="attribute" name="playhead" type="show">
+                            <span>Show playhead: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                        <div class="attribute" name="page-count" type="show">
+                            <span>Show Page Count: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                        <div class="attribute" name="comments" type="show">
+                            <span>Show Fragment Comments: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div style="text-align: center;">
+            <button type="button" class="btn btn-success" ng-click="addPage()">Add Page</button>
+        </div>
+        <div class="node pageNode" ng-controller="page" ng-repeat="page in specification.pages">
+            <h2>Page</h2>
+            <button type="button" class="btn btn-danger" ng-click="removePage(page)">Remove Page</button>
+            <div class="attributes">
+                <div class="attribute">
+                    <span>Unique ID: </span>
+                    <input type="text" ng-model="page.id" required/>
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Define the common root of each fragment URL. For example if every fragment URL starts with 'http://example.org/media/' then this can be placed here and the remainder for each fragment in their respective URL boxed.">
+                    <span>Fragment common-root URL: </span>
+                    <input type="text" ng-model="page.hostURL" />
+                </div>
+                <div class="attribute">
+                    <span>Randomise Fragment Order: </span>
+                    <input type="checkbox" ng-model="page.randomiseOrder" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Specify if this page should be repeated and how many times. Please note, that if page-pooling is also selected then it 'may' not repeat as many times.">
+                    <span>Repeat Page N-times: </span>
+                    <input type="number" ng-model="page.repeatCount" value="0" step="1" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Loop audio playback until manually stopped or the page submit button is pressed">
+                    <span>Loop audio: </span>
+                    <input type="checkbox" ng-model="page.loop" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Synchronise playback of each fragment in this page. If all fragments have the same audio (such as mix evaluations) then this can enable users to seemlessly transition. Otherwise audio will start from the beginning of each fragment">
+                    <span>Synchronous audio playback: </span>
+                    <input type="checkbox" ng-model="page.synchronous" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Over-ride global loudness normalisation">
+                    <span>Loudness (page): </span>
+                    <input type="number" ng-model="page.loudness" max="0" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Label type to display on the fragments.">
+                    <span>Label type: </span>
+                    <select ng-model="page.label">
+                        <option value="default">Default</option>
+                        <option value="none">None</option>
+                        <option value="number">[1, 2, 3...]</option>
+                        <option value="letter">[a, b, c...]</option>
+                        <option value="capital">[A, B, C...]</option>
+                        <option value="samediff" ng-show="specification.interface == 'AB'">[Same, Different]</option>
+                    </select>
+                </div>
+                <div class="attribute" ng-show="page.label != 'default' && page.label != 'none'">
+                    <span>Label Start: </span>
+                    <input type="text" ng-model="page.labelStart" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Select a subgroup of the given audio fragments to display. 0 or blank means all fragments will be displayed.">
+                    <span>Fragment pool size: </span>
+                    <input type="number" ng-model="page.poolSize" min="0" max="page.audioElements.length" />
+                </div>
+                <div class="attribute" ng-show="specification.poolSize > 0" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Always display this page, even after sub-pooling of pages">
+                    <span>Always include page: </span>
+                    <input type="checkbox" ng-model="page.alwaysInclude" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Always show this page in this position. Useful for training pages to ensure they are always positioned first.">
+                    <span>Fixed Page Position: </span>
+                    <input type="number" ng-model="page.position" min="0" max="{{specification.pages.length}}" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Over-ride global pre-silence">
+                    <span>Fragment pre-silence: </span>
+                    <input type="number" ng-model="page.preSilence" min="0" step="0.1" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Over-ride global post-silence">
+                    <span>Fragment post-silence: </span>
+                    <input type="number" ng-model="page.postSilence" min="0" step="0.1" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Disable switching of audio">
+                    <span>Cannot interupt audio: </span>
+                    <input type="checkbox" ng-model="page.playOne" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Only allow playing fragments' interface handle to be manipulated during playback.">
+                    <span>Only move playing audio: </span>
+                    <input type="checkbox" ng-model="page.restrictMovement" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Over-ride global minimum number of fragment plays">
+                    <span>Minimum number of fragment plays</span>
+                    <input type="number" ng-model="page.minNumberPlays" min="0" max="{{page.maxNumberPlays || specification.maxNumberPlays}}" />
+                </div>
+                <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Over-ride global maximum number of fragment plays">
+                    <span>Maximum number of fragment plays</span>
+                    <input type="number" ng-model="page.maxNumberPlays" min="{{page.minNumberPlays || specification.minNumberPlays || 0}}" />
+                </div>
+            </div>
+            <div class="node" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Set the title of the page">
+                <h3>Page Title</h3>
+                <textarea ng-model="page.title"></textarea>
+            </div>
+            <div class="node">
+                <h3>Comment box text prefix</h3>
+                <textarea ng-model="page.commentboxprefix"></textarea>
+                <p>Example:
+                    <span style="font-weight:600">{{page.commentboxprefix}} A</span>
+                </p>
+            </div>
+            <div class="node" ng-controller="survey" ng-init="survey = page.preTest">
+                <h2>Pre Page Survey</h2>
+                <button type="button" class="btn btn-success" ng-click="addSurveyEntry()">Add Entry</button>
+                <div class="node" ng-repeat="opt in survey.options" ng-controller="surveyOption">
+                    <h3>Survey Entry</h3>
+                    <button type="button" class="btn btn-danger" ng-click="removeSurveyEntry(opt);">Delete Entry</button>
+                    <div class="attributes">
+                        <div class="attribute">
+                            <span>Survey Type: </span>
+                            <select ng-model="opt.type">
+                                <option value="question">Question</option>
+                                <option value="radio">Radio</option>
+                                <option value="checkbox">Checkbox</option>
+                                <option value="statement">Statement</option>
+                                <option value="number">Number</option>
+                                <option value="slider">Slider</option>
+                                <option value="video">Video</option>
+                                <option value="youtube">YouTube</option>
+                            </select>
+                        </div>
+                        <div class="attribute">
+                            <span>Unique Survey Entry ID:</span>
+                            <input type="text" ng-model="opt.id" required/>
+                        </div>
+                        <div class="attribute">
+                            <span>Entry Name:</span>
+                            <input type="text" ng-model="opt.name" />
+                        </div>
+                        <div class="attribute" ng-show="['question', 'checkbox', 'radio', 'number'].indexOf(opt.type) >= 0">
+                            <span>Mandatory:</span>
+                            <input type="checkbox" ng-model="opt.mandatory" />
+                        </div>
+                        <div class="attribute">
+                            <span>Minimum Wait Time (s):</span>
+                            <input type="number" ng-model="opt.minWait" min="0" />
+                        </div>
+                        <div class="attribute" ng-show="opt.type == 'question'">
+                            <span>Box Size:</span>
+                            <select ng-model="opt.boxsize">
+                                <option value="small">Small</option>
+                                <option value="normal">Normal</option>
+                                <option value="large">Large</option>
+                                <option value="huge">Huge</option>
+                            </select>
+                        </div>
+                        <div class="attribute" ng-show="['checkbox', 'radio'].indexOf(opt.type) >= 0">
+                            <span>Minimum Selected:</span>
+                            <input type="number" ng-model="opt.min" min="0" />
+                        </div>
+                        <div class="attribute" ng-show="['checkbox', 'radio'].indexOf(opt.type) >= 0">
+                            <span>Maximum Selected:</span>
+                            <input type="number" ng-model="opt.max" max="{{opt.options.length}}" />
+                        </div>
+                        <div class="attribute" ng-show="['slider', 'number'].indexOf(opt.type) >= 0">
+                            <span>Minimum Value:</span>
+                            <input type="number" ng-model="opt.min" />
+                        </div>
+                        <div class="attribute" ng-show="['slider', 'number'].indexOf(opt.type) >= 0">
+                            <span>Maximum Value:</span>
+                            <input type="number" ng-model="opt.max" />
+                        </div>
+                        <div class="attribute" ng-show="['video', 'youtube'].indexOf(opt.type) >= 0">
+                            <span>Video URL:</span>
+                            <input type="text" ng-model="opt.url" />
+                        </div>
+                    </div>
+                    <div class="node">
+                        <h4>Statement</h4>
+                        <textarea ng-model="opt.statement"></textarea>
+                    </div>
+                    <div class="node" ng-show="['checkbox', 'radio'].indexOf(opt.type) >= 0">
+                        <h4>Options</h4>
+                        <div>
+                            <button type="button" class="btn btn-default" ng-click="addOption();">Add Option</button>
+                        </div>
+                        <div class="node" ng-repeat="option in opt.options">
+                            <div class="attributes">
+                                <div class="attribute">
+                                    <button type="button" class="btn btn-default" ng-click="removeOption(option);">Remove</button>
+                                </div>
+                                <div class="attribute">
+                                    <span>Name: </span>
+                                    <input type="text" ng-model="option.name" required/>
+                                </div>
+                                <div class="attribute">
+                                    <span>Displayed Text: </span>
+                                    <input type="text" ng-model="option.text" required/>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="node">
+                        <h4>Conditionals</h4>
+                        <button type="button" class="btn btn-default" ng-click="addCondition()">Add Condition</button>
+                        <div class="node" ng-repeat="condition in opt.conditions">
+                            <div class="attributes">
+                                <div class="attribute">
+                                    <button type="button" class="btn btn-danger" ng-click="removeCondition(condition)">Remove</button>
+                                </div>
+                                <div class="attribute">
+                                    <span>Check Type:</span>
+                                    <select ng-model="condition.check">
+                                        <option value="equals">Equal To</option>
+                                        <option value="lessThan">Less Than</option>
+                                        <option value="greaterThan">Greater Than</option>
+                                        <option value="stringContains">String Contains</option>
+                                    </select>
+                                </div>
+                                <div class="attribute">
+                                    <span>Value: </span>
+                                    <input type="text" ng-model="condition.value" required/>
+                                </div>
+                                <div class="attribute">
+                                    <span>Jump To On Pass: </span>
+                                    <select ng-model="condition.jumpToOnPass">
+                                        <option value="">None</option>
+                                        <option ng-repeat="entry in survey.options" value="{{entry.id}}">{{entry.id}}</option>
+                                    </select>
+                                </div>
+                                <div class="attribute">
+                                    <span>Jump To On Fail: </span>
+                                    <select ng-model="condition.jumpToOnFail">
+                                        <option value="">None</option>
+                                        <option ng-repeat="entry in survey.options" value="{{entry.id}}">{{entry.id}}</option>
+                                    </select>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="node" ng-controller="survey" ng-init="survey = page.postTest">
+                <h2>Post Page Survey</h2>
+                <button type="button" class="btn btn-success" ng-click="addSurveyEntry()">Add Entry</button>
+                <div class="node" ng-repeat="opt in survey.options" ng-controller="surveyOption">
+                    <h3>Survey Entry</h3>
+                    <button type="button" class="btn btn-danger" ng-click="removeSurveyEntry(opt);">Delete Entry</button>
+                    <div class="attributes">
+                        <div class="attribute">
+                            <span>Survey Type: </span>
+                            <select ng-model="opt.type">
+                                <option value="question">Question</option>
+                                <option value="radio">Radio</option>
+                                <option value="checkbox">Checkbox</option>
+                                <option value="statement">Statement</option>
+                                <option value="number">Number</option>
+                                <option value="slider">Slider</option>
+                                <option value="video">Video</option>
+                                <option value="youtube">YouTube</option>
+                            </select>
+                        </div>
+                        <div class="attribute">
+                            <span>Unique Survey Entry ID:</span>
+                            <input type="text" ng-model="opt.id" required />
+                        </div>
+                        <div class="attribute">
+                            <span>Entry Name:</span>
+                            <input type="text" ng-model="opt.name" />
+                        </div>
+                        <div class="attribute" ng-show="['question', 'checkbox', 'radio', 'number'].indexOf(opt.type) >= 0">
+                            <span>Mandatory:</span>
+                            <input type="checkbox" ng-model="opt.mandatory" />
+                        </div>
+                        <div class="attribute">
+                            <span>Minimum Wait Time (s):</span>
+                            <input type="number" ng-model="opt.minWait" min="0" />
+                        </div>
+                        <div class="attribute" ng-show="opt.type == 'question'">
+                            <span>Box Size:</span>
+                            <select ng-model="opt.boxsize">
+                                <option value="small">Small</option>
+                                <option value="normal">Normal</option>
+                                <option value="large">Large</option>
+                                <option value="huge">Huge</option>
+                            </select>
+                        </div>
+                        <div class="attribute" ng-show="['checkbox', 'radio'].indexOf(opt.type) >= 0">
+                            <span>Minimum Selected:</span>
+                            <input type="number" ng-model="opt.min" min="0" />
+                        </div>
+                        <div class="attribute" ng-show="['checkbox', 'radio'].indexOf(opt.type) >= 0">
+                            <span>Maximum Selected:</span>
+                            <input type="number" ng-model="opt.max" max="{{opt.options.length}}" />
+                        </div>
+                        <div class="attribute" ng-show="['slider', 'number'].indexOf(opt.type) >= 0">
+                            <span>Minimum Value:</span>
+                            <input type="number" ng-model="opt.min" />
+                        </div>
+                        <div class="attribute" ng-show="['slider', 'number'].indexOf(opt.type) >= 0">
+                            <span>Maximum Value:</span>
+                            <input type="number" ng-model="opt.max" />
+                        </div>
+                        <div class="attribute" ng-show="['video', 'youtube'].indexOf(opt.type) >= 0">
+                            <span>Video URL:</span>
+                            <input type="text" ng-model="opt.url" />
+                        </div>
+                    </div>
+                    <div class="node">
+                        <h4>Statement</h4>
+                        <textarea ng-model="opt.statement"></textarea>
+                    </div>
+                    <div class="node" ng-show="['checkbox', 'radio'].indexOf(opt.type) >= 0">
+                        <h4>Options</h4>
+                        <div>
+                            <button type="button" class="btn btn-default" ng-click="addOption();">Add Option</button>
+                        </div>
+                        <div class="node" ng-repeat="option in opt.options">
+                            <div class="attributes">
+                                <div class="attribute">
+                                    <button type="button" class="btn btn-default" ng-click="removeOption(option);">Remove</button>
+                                </div>
+                                <div class="attribute">
+                                    <span>Name: </span>
+                                    <input type="text" ng-model="option.name" required/>
+                                </div>
+                                <div class="attribute">
+                                    <span>Displayed Text: </span>
+                                    <input type="text" ng-model="option.text" required/>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="node">
+                        <h4>Conditionals</h4>
+                        <button type="button" class="btn btn-default" ng-click="addCondition()">Add Condition</button>
+                        <div class="node" ng-repeat="condition in opt.conditions">
+                            <div class="attributes">
+                                <div class="attribute">
+                                    <button type="button" class="btn btn-danger" ng-click="removeCondition(condition)">Remove</button>
+                                </div>
+                                <div class="attribute">
+                                    <span>Check Type:</span>
+                                    <select ng-model="condition.check">
+                                        <option value="equals">Equal To</option>
+                                        <option value="lessThan">Less Than</option>
+                                        <option value="greaterThan">Greater Than</option>
+                                        <option value="stringContains">String Contains</option>
+                                    </select>
+                                </div>
+                                <div class="attribute">
+                                    <span>Value: </span>
+                                    <input type="text" ng-model="condition.value" required/>
+                                </div>
+                                <div class="attribute">
+                                    <span>Jump To On Pass: </span>
+                                    <select ng-model="condition.jumpToOnPass">
+                                        <option value="">None</option>
+                                        <option ng-repeat="entry in survey.options" value="{{entry.id}}">{{entry.id}}</option>
+                                    </select>
+                                </div>
+                                <div class="attribute">
+                                    <span>Jump To On Fail: </span>
+                                    <select ng-model="condition.jumpToOnFail">
+                                        <option value="">None</option>
+                                        <option ng-repeat="entry in survey.options" value="{{entry.id}}">{{entry.id}}</option>
+                                    </select>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <button type="button" class="btn btn-success" ng-show="specification.interface == 'APE' || page.interfaces.length == 0" ng-click="addInterface()">Add Interface/Axis</button>
+            <div class="node" ng-repeat="interface in page.interfaces" ng-controller="interfaceNode">
+                <h2>Interface</h2>
+                <button type="button" class="btn btn-danger" ng-click="removeInterface(interface)">Remove Interface/Axis</button>
+                <div class="node interfaceOptions">
+                    <div class="attributes">
+                        <div class="attribute" name="fragmentPlayed" type="check">
+                            <span>Check all fragments played: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                        <div class="attribute" name="fragmentFullPlayback" type="check">
+                            <span>Check all fragments fully played: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                        <div class="attribute" name="fragmentMoved" type="check">
+                            <span>Check all fragments have been moved: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                        <div class="attribute" name="fragmentComments" type="check">
+                            <span>Check all fragments have comments: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                        <div class="attribute" name="scalerange" type="check">
+                            <span>Enforce a scale usage: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                            <span>Minimum:</span>
+                            <input type="number" min="0" max="100" name="min" />
+                            <span>Maximum:</span>
+                            <input type="number" min="0" max="100" name="max" />
+                        </div>
+                        <div class="attribute" name="volume" type="show">
+                            <span>Show master volume control: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                        <div class="attribute" name="playhead" type="show">
+                            <span>Show playhead: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                        <div class="attribute" name="page-count" type="show">
+                            <span>Show Page Count: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                        <div class="attribute" name="comments" type="show">
+                            <span>Show Fragment Comments: </span>
+                            <input type="checkbox" ng-click="enableInterfaceOption($event)" />
+                        </div>
+                    </div>
+                </div>
+                <div class="node">
+                    <h3>Axis Title</h3>
+                    <textarea ng-model="interface.title"></textarea>
+                    <div class="attributes">
+                        <div class="attribute">
+                            <span>Axis name (in saves): </span>
+                            <input type="text" ng-model="interface.name" />
+                        </div>
+                    </div>
+                </div>
+                <div class="node">
+                    <h3>Axis Image</h3>
+                    <textarea ng-model="interface.image"></textarea>
+                </div>
+                <div class="node" name="scale-selection">
+                    <h3>Axis Scales</h3>
+                    <button type="button" class="btn btn-success" ng-click="addScale();">Add</button>
+                    <button type="button" class="btn btn-danger" ng-click="clearScales()" ng-show="interface.scales.length > 0">Clear Scales</button>
+                    <div class="dropdown" style="display: inline-block;">
+                        <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
+                            Scales: {{selectedScale}}
+                            <span class="caret"></span>
+                        </button>
+                        <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
+                            <li ng-repeat="scale in scales" ng-click="useScales(scale)"><a href="#">{{scale.name}}</a></li>
+                        </ul>
+                    </div>
+                    <div class="node" ng-repeat="scale in interface.scales">
+                        <div class="attributes">
+                            <div class="attribute">
+                                <button type="button" class="btn btn-danger" ng-click="removeScale(scale);">Remove</button>
+                            </div>
+                            <div class="attribute">
+                                <span>Position: </span>
+                                <input type="number" min="0" max="100" ng-model="scale.position" required/>
+                            </div>
+                            <div class="attribute">
+                                <span>Text: </span>
+                                <input type="text" ng-model="scale.text" required/>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="node">
+                <h3>Comment Questions</h3>
+                <button type="button" class="btn btn-success" ng-click="addCommentQuestion()">Add Comment Question</button>
+                <div class="node" ng-repeat="cq in page.commentQuestions">
+                    <button type="button" class="btn btn-danger" ng-click="removeCommentQuestion(cq)">Remove Comment Question</button>
+                    <div class="attributes">
+                        <div class="attribute">
+                            <span>Unique ID:</span>
+                            <input type="text" ng-model="cq.id" required/>
+                        </div>
+                        <div class="attribute">
+                            <span>Common Name:</span>
+                            <input type="text" ng-model="cq.name" />
+                        </div>
+                        <div class="attribute" ng-show="cq.type == 'slider'">
+                            <span>Minimum:</span>
+                            <input type="number" ng-model="cq.min" />
+                        </div>
+                        <div class="attribute" ng-show="cq.type == 'slider'">
+                            <span>Maximum:</span>
+                            <input type="number" ng-model="cq.max" />
+                        </div>
+                        <div class="attribute" ng-show="cq.type == 'slider'">
+                            <span>Step size:</span>
+                            <input type="number" ng-model="cq.step" />
+                        </div>
+                        <div class="attribute" ng-show="cq.type == 'slider'">
+                            <span>Initial Value:</span>
+                            <input type="number" ng-model="cq.value" />
+                        </div>
+                    </div>
+                    <div class="node">
+                        <h4>Question:</h4>
+                        <textarea ng-model="cq.statement"></textarea>
+                    </div>
+                    <div class="node" ng-show="['radio', 'checkbox'].indexOf(cq.type) >= 0">
+                        <h4>Options</h4>
+                        <div class="node" ng-repeat="option in cq.options">
+                            <div class="attributes">
+                                <div class="attribute">
+                                    <button type="button" class="btn btn-danger" ng-click="removeCommentQuestionOption(cq,option)">Remove</button>
+                                </div>
+                                <div class="attribute">
+                                    <span>Name: </span>
+                                    <input type="text" ng-model="option.name" required/>
+                                </div>
+                                <div class="attribute">
+                                    <span>Display Text: </span>
+                                    <input type="text" ng-model="option.text" required/>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <button type="button" class="btn btn-success" ng-click="addAudioElement()">Add Fragment</button>
+            <div class="node" ng-repeat="fragment in page.audioElements">
+                <h3>Audio Fragment</h3>
+                <button type="button" class="btn btn-danger" ng-click="removeAudioElement(fragment)">Remove Fragment</button>
+                <div class="attributes">
+                    <div class="attribute">
+                        <span>Unique ID: </span>
+                        <input type="text" ng-model="fragment.id" required/>
+                    </div>
+                    <div class="attribute">
+                        <span>URL: </span>
+                        <input type="text" ng-model="fragment.url" required/>
+                        <span>Full URL: </span><span style="font-weight=600">{{page.hostURL}}{{fragment.url}}</span>
+                    </div>
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Set the gain of this fragment. This is applied after any normalisation">
+                        <span>Fragment Gain (dB): </span>
+                        <input type="number" ng-model="fragment.gain" />
+                    </div>
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Manually set the label">
+                        <span>Fragment Label: </span>
+                        <input type="text" ng-model="fragment.label" />
+                    </div>
+                    <div class="attribute">
+                        <span>Fragment Common name: </span>
+                        <input type="text" ng-model="fragment.name" />
+                    </div>
+                    <div class="attribute">
+                        <span>Fragment Type: </span>
+                        <select ng-model="fragment.type">
+                            <option value="normal">Normal</option>
+                            <option value="anchor">Hidden Anchor</option>
+                            <option value="reference">Hidden Reference</option>
+                            <option value="outside-reference">Outside Reference</option>
+                        </select>
+                    </div>
+                    <div class="attribute" ng-show="fragment.type == 'anchor'">
+                        <span>Anchor must be below: </span>
+                        <input type="number" ng-model="fragment.marker" min="0" max="100" />
+                    </div>
+                    <div class="attribute" ng-show="fragment.type == 'reference'">
+                        <span>Reference must be above: </span>
+                        <input type="number" ng-model="fragment.marker" min="0" max="100" />
+                    </div>
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Over-ride global and page loudness">
+                        <span>Loudness: </span>
+                        <input type="number" ng-model="fragment.loudness" max="0" />
+                    </div>
+                    <div class="attribute" ng-show="page.poolSize > 0" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Always include this fragment after any sub-pooling">
+                        <span>Always include fragment: </span>
+                        <input type="checkbox" ng-model="fragment.alwaysInclude" max="0" />
+                    </div>
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Over-ride global / page pre-silence">
+                        <span>Fragment Pre-Silence: </span>
+                        <input type="number" ng-model="fragment.preSilence" max="0" step="0.1" />
+                    </div>
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Over-ride global / page post-silence">
+                        <span>Fragment Post-Silence: </span>
+                        <input type="number" ng-model="fragment.postSilence" max="0" step="0.1" />
+                    </div>
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="By default the fragment will start playback at the beginning.">
+                        <span>Fragment playback start position (s): </span>
+                        <input type="number" ng-model="fragment.startTime" min="0" max="{{fragment.stopTime}}" />
+                    </div>
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="By default the fragment will play until the end">
+                        <span>Fragment playback stop position (s): </span>
+                        <input type="number" ng-model="fragment.stopTime" min="{{fragment.startTime}}" />
+                    </div>
+                    <div class="attribute">
+                        <span>Fragment sampling rate: </span>
+                        <input type="number" ng-model="fragment.sampleRate" min="1" />
+                    </div>
+                    <div class="attribute" data-container="body" data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="Associate an image with this fragment">
+                        <span>Fragment Image (URL): </span>
+                        <input type="text" ng-model="fragment.image" />
+                    </div>
+                    <div class="attribute">
+                        <span>Minimum number of plays</span>
+                        <input type="number" ng-model="fragment.minNumberPlays" min="0" max="{{fragment.maxNumberPlays || page.maxNumberPlays || specification.maxNumberPlays}}" />
+                    </div>
+                    <div class="attribute">
+                        <span>Maximum number of plays</span>
+                        <input type="number" ng-model="fragment.maxNumberPlays" min="{{fragment.minNumberPlays || page.minNumberPlays || specification.minNumberPlays || 0}}" />
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div id="popupHolder" ng-show="popupVisible">
+        <div ng-controller="introduction" class="popup" ng-show="popupVisible">
+            <div class="popupTitle" ng-switch="state">
+                <span ng-switch-when="0">Test Creator</span>
+                <span ng-switch-when="1">Create New Test</span>
+            </div>
+            <div class="popupContent container-fluid" ng-switch="state">
+                <div ng-switch-when="0">
+                    <div>
+                        <span>Welcome to the WAET test creator tool. This will allow you to create a new test from scratch to suit your testing needs. If you wish to update a test file, please drag and drop the XML document into the area below for processing, otherwise press 'Next' to start a new test. This tool generates files for the WAET 1.2.1 version.</span>
+                    </div>
+                    <div>
+                        <input type="file" id="files" ng-model="files" onchange="handleFiles(event)" />
+                    </div>
+                </div>
+                <div ng-switch-when="1">
+                    <div>
+                        <span>Please select the interface you would like to use below. Selecting an interface will give a brief description of the interface type.</span>
+                    </div>
+                    <div class="row">
+                        <div class="col-md-6" style="overflow-y: scroll;height: 333px;">
+                            <div class="new-test" ng-repeat="i in testSpecifications.interfaces" ng-mouseover="mouseover(i.name)" ng-click="initialise(i.name)">
+                                <label style="cursor:pointer">
+                                    <input type="radio" name="new-test" value="{{i.name}}" id="i.name" style="cursor:pointer" /> {{i.name}}
+                                </label>
+                            </div>
+                        </div>
+                        <div class="col-md-6">
+                            <span>{{description}}</span>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="popupButtons">
+                <button id="popupBack" type="button" class="btn btn-default" ng-show="state>0" ng-click="back()">Back</button>
+                <button id="popupNext" type="button" class="btn btn-default" ng-click="next()">Next</button>
+            </div>
+        </div>
+    </div>
+    <div id="screenblank" ng-show="popupVisible"></div>
+</body>
+
+</html>
--- a/test_create/attributes.json	Tue Jun 27 21:22:15 2017 +0100
+++ b/test_create/attributes.json	Wed Jul 05 12:22:29 2017 +0100
@@ -1,35 +1,1 @@
-{
-    "id": "ID",
-    "mandatory": "Mandatory",
-    "name": "Name",
-    "interface": "Interface Module",
-    "projectReturn": "Save Return URL",
-    "returnURL": "On complete redirect URL",
-    "randomiseOrder": "Randomise Order",
-    "testPages": "Test Pages",
-    "loudness": "Target Loudness (LUFS)",
-    "sampleRate": "Required Sample Rate",
-    "hostURL": "Element URL Prefix",
-    "repeatCount": "Repeat Count",
-    "loop": "Loop playback",
-    "synchronous": "Synchronous playback",
-    "type": "Type",
-    "min": "Minimum",
-    "max": "Maximum",
-    "position": "Position",
-    "url": "URL",
-    "gain": "Gain (dB)",
-    "marker": "Marker",
-    "boxsize": "Box Size",
-    "label": "Label",
-    "calibration": "Perform Calibration",
-    "preSilence": "Pre Silence",
-    "postSilence": "Post Silence",
-    "poolSize": "Pool Size",
-    "alwaysInclude": "Always Include",
-    "crossFade": "Cross Fade",
-    "check": "Check",
-    "value": "Value",
-    "jumpToOnPass": "Jump To ID On Pass",
-    "jumpToOnFail": "Jump To ID On Fail"
-}
+
--- a/test_create/custom.css	Tue Jun 27 21:22:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-div#content > div.node {
-    background-color: rgb(200, 228, 151);
-}
-div#content > div#setup {
-    background-color: coral;
-}
-input:disabled+span {
-    text-decoration: line-through;
-}
-div.attribute {
-    float: none;
-}
-div.attribute input {
-    max-width: 100%;
-    width: 300px;
-}
-div.attribute input[type=radio],
-div.attribute input[type=checkbox] {
-    width: 10px;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test_create/interfaces/specifications.json	Wed Jul 05 12:22:29 2017 +0100
@@ -0,0 +1,573 @@
+{
+    "interfaces": [
+        {
+            "name": "Audio Perceptual Evaluation (APE)",
+            "interface": "APE",
+            "description": {
+                "en": "Audio Perceptual Evaluation. A multi-stimulus test where each audio fragment is shown on one continuous slider. Fragments are randomnly positioned along the slider. The user clicks a fragment to play and drags to move."
+            },
+            "checks": [],
+            "show": [],
+            "elements": []
+        }, {
+            "name": "MUSHRA",
+            "interface": "MUSHRA",
+            "description": {
+                "en": "Multi-stimulus with hidden reference and anchor. Each fragment is shown on its own vertical slider. One fragment must be labelled as a reference and another labelled as an anchor. One external reference must also be shown."
+            },
+            "scales": ["ACR"],
+            "checks": [{
+                "name": "fragmentMoved",
+                "support": "none"
+            }, {
+                "name": "fragmentPlayed",
+                "support": "none"
+            }, {
+                "name": "fragmentFullPlayback",
+                "support": "none"
+            }, {
+                "name": "fragmentComments",
+                "support": "none"
+            }, {
+                "name": "scalerange",
+                "support": "none"
+            }],
+            "show": [{
+                "name": "volume",
+                "support": "none"
+            }, {
+                "name": "page-count",
+                "support": "none"
+            }, {
+                "name": "playhead",
+                "support": "none"
+            }, {
+                "name": "comments",
+                "support": "none"
+            }],
+            "elements": [{
+                "anchor": {
+                    "min": 1,
+                    "max": "undefined"
+                },
+                "reference": {
+                    "min": 1,
+                    "max": "undefined"
+                },
+                "outsidereference": {
+                    "min": 1,
+                    "max": "undefined"
+                }
+            }]
+        }, {
+            "name": "Vertical Sliders",
+            "interface": "MUSHRA",
+            "description": {
+                "en": "Each element is given its own vertical slider with user defined scale markers."
+            }
+        }, {
+            "name": "Horizontal Sliders",
+            "interface": "horizontal",
+            "description": {
+                "en": "Each element is given its own horizontal slider with user defined scale markers."
+            }
+        }, {
+            "name": "Discrete",
+            "interface": "discrete",
+            "description": {
+                "en": "Each element is given a horizontal scale broken into a number of discrete choices. The number of choices is defined by the scale markers."
+            }
+        }, {
+            "name": "Rank",
+            "interface": "ordinal",
+            "description": {
+                "en": "Each stimulus is placed on a discrete scale equalling the number of fragments. The fragments are then ranked based on the question posed. Only one element can occupy a rank position"
+            }
+        }, {
+            "name": "Likert",
+            "interface": "discrete",
+            "description": {
+                "en": "Each stimulus is placed on a discrete scale. The scale is fixed to the Likert scale options of 'Strongly Disagree', 'Disagree', 'Neutral', 'Agree' and 'Strongly Agree'"
+            },
+            "scales": ["Likert"],
+            "checks": [{
+                "name": "fragmentPlayed",
+                "support": "none"
+            }, {
+                "name": "fragmentFullPlayback",
+                "support": "none"
+            }, {
+                "name": "fragmentComments",
+                "support": "none"
+            }],
+            "show": [{
+                "name": "volume",
+                "support": "none"
+            }, {
+                "name": "page-count",
+                "support": "none"
+            }, {
+                "name": "playhead",
+                "support": "none"
+            }, {
+                "name": "comments",
+                "support": "none"
+            }]
+        }, {
+            "name": "ABC/HR",
+            "interface": "MUSHRA",
+            "description": {
+                "en": "Each stimulus is placed on a vertical slider. The scale is fixed with the labels 'Imperceptible' to 'Very Annoying'"
+            },
+            "scales": ["ABC"],
+            "checks": [{
+                "name": "fragmentMoved",
+                "support": "none"
+            }, {
+                "name": "fragmentPlayed",
+                "support": "none"
+            }, {
+                "name": "fragmentFullPlayback",
+                "support": "none"
+            }, {
+                "name": "fragmentComments",
+                "support": "none"
+            }],
+            "show": [{
+                "name": "volume",
+                "support": "none"
+            }, {
+                "name": "page-count",
+                "support": "none"
+            }, {
+                "name": "playhead",
+                "support": "none"
+            }, {
+                "name": "comments",
+                "support": "none"
+            }]
+        }, {
+            "name": "Bipolar",
+            "interface": "horizontal",
+            "description": {
+                "en": "Each stimulus is placed on a horizontal slider and initialised to the value '0'. The scale operates from -50 to +5-. In the results this is normalised, like all other interfaces, from 0 (-50) to 1 (+50)"
+            },
+            "scales": ["Bipolar"],
+            "checks": [{
+                "name": "fragmentMoved",
+                "support": "none"
+            }, {
+                "name": "fragmentPlayed",
+                "support": "none"
+            }, {
+                "name": "fragmentFullPlayback",
+                "support": "none"
+            }, {
+                "name": "fragmentComments",
+                "support": "none"
+            }],
+            "show": [{
+                "name": "volume",
+                "support": "none"
+            }, {
+                "name": "page-count",
+                "support": "none"
+            }, {
+                "name": "playhead",
+                "support": "none"
+            }, {
+                "name": "comments",
+                "support": "none"
+            }]
+        }, {
+            "name": "Absolute Category Rating",
+            "interface": "discrete",
+            "description": {
+                "en": "Each element is on a discrete scale of 'Bad', 'Poor', 'Fair', 'Good' and 'Excellent'. Each element must be given a rating."
+            },
+            "scales": ["ACR"],
+            "checks": [{
+                "name": "fragmentMoved",
+                "support": "mandatory"
+            }, {
+                "name": "fragmentPlayed",
+                "support": "none"
+            }, {
+                "name": "fragmentFullPlayback",
+                "support": "none"
+            }, {
+                "name": "fragmentComments",
+                "support": "none"
+            }],
+            "show": [{
+                "name": "volume",
+                "support": "none"
+            }, {
+                "name": "page-count",
+                "support": "none"
+            }, {
+                "name": "playhead",
+                "support": "none"
+            }, {
+                "name": "comments",
+                "support": "none"
+            }]
+        }, {
+            "name": "Discrete Category Rating",
+            "interface": "discrete",
+            "description": {
+                "en": ""
+            },
+            "scales": ["DCR"],
+            "checks": [{
+                "name": "fragmentPlayed",
+                "support": "none"
+            }, {
+                "name": "fragmentFullPlayback",
+                "support": "none"
+            }, {
+                "name": "fragmentComments",
+                "support": "none"
+            }],
+            "show": [{
+                "name": "volume",
+                "support": "none"
+            }, {
+                "name": "page-count",
+                "support": "none"
+            }, {
+                "name": "playhead",
+                "support": "none"
+            }, {
+                "name": "comments",
+                "support": "none"
+            }]
+        }, {
+            "name": "Hedonic Cat. Rating",
+            "interface": "MUSHRA",
+            "description": {
+                "en": ""
+            },
+            "scales": ["Hedonic Category Rating Scale"],
+            "checks": [{
+                "name": "fragmentMoved",
+                "support": "mandatory"
+            }, {
+                "name": "fragmentPlayed",
+                "support": "none"
+            }, {
+                "name": "fragmentFullPlayback",
+                "support": "none"
+            }, {
+                "name": "fragmentComments",
+                "support": "none"
+            }],
+            "show": [{
+                "name": "volume",
+                "support": "none"
+            }, {
+                "name": "page-count",
+                "support": "none"
+            }, {
+                "name": "playhead",
+                "support": "none"
+            }, {
+                "name": "comments",
+                "support": "none"
+            }],
+            "elements": {
+                "outsidereference": {
+                    "min": 1,
+                    "max": 1
+                }
+            }
+        }, {
+            "name": "ITUR5PCIS",
+            "interface": "MUSHRA",
+            "description": {
+                "en": ""
+            },
+            "scales": ["ABC"],
+            "checks": [{
+                "name": "fragmentMoved",
+                "support": "none"
+            }, {
+                "name": "fragmentPlayed",
+                "support": "none"
+            }, {
+                "name": "fragmentFullPlayback",
+                "support": "none"
+            }, {
+                "name": "fragmentComments",
+                "support": "none"
+            }],
+            "show": [{
+                "name": "volume",
+                "support": "none"
+            }, {
+                "name": "page-count",
+                "support": "none"
+            }, {
+                "name": "playhead",
+                "support": "none"
+            }, {
+                "name": "comments",
+                "support": "none"
+            }],
+            "elements": {
+                "outsidereference": {
+                    "min": 1,
+                    "max": 1
+                }
+            }
+        }, {
+            "name": "Pairwise",
+            "interface": "AB",
+            "description": {
+                "en": "A discrete interface where each page holds each fragment. The user must select one fragment. All other fragments are not selected"
+            },
+            "hasScales": "false",
+            "elements": {
+                "number": {
+                    "min": 2,
+                    "max": "undefined"
+                }
+            }
+        }, {
+            "name": "AB",
+            "interface": "AB",
+            "description": {
+                "en": "Each page contains two audio fragments. The user must select one of the fragments to proceed. There can be an outside reference."
+            },
+            "hasScales": "false",
+            "checks": [{
+                "name": "fragmentPlayed",
+                "support": "mandatory"
+            }],
+            "elements": {
+                "number": {
+                    "min": 2,
+                    "max": 2
+                },
+                "outsidereference": {
+                    "min": 0,
+                    "max": 1
+                }
+            }
+        }, {
+            "name": "ABX",
+            "interface": "ABX",
+            "description": {
+                "en": "Each page has two audio fragments presented as A and B. The test duplicates one of the fragments and presents it as X. The user must choose which, out of A or B, is closest to X."
+            },
+            "hasScales": "false",
+            "checks": [{
+                "name": "fragmentPlayed",
+                "support": "mandatory"
+            }],
+            "elements": {
+                "number": {
+                    "min": 2,
+                    "max": 2
+                },
+                "outsidereference": {
+                    "min": 0,
+                    "max": 1
+                }
+            }
+        }, {
+            "name": "Timeline",
+            "interface": "timeline",
+            "description": {
+                "en": "Each fragment is displayed with a clickable waveform of itself. The user must click on the waveform at the location that a specific event occured. Users can then enter in information about this event. This test is unit-/value-less."
+            }
+        }
+    ],
+    "scales": [
+        {
+            "name": "Likert",
+            "scales": [
+                {
+                    "text": "Strongly Disagree",
+                    "position": 0
+                },
+                {
+                    "text": "Disagree",
+                    "position": 25
+                },
+                {
+                    "text": "Neutral",
+                    "position": 50
+                },
+                {
+                    "text": "Agree",
+                    "position": 75
+                },
+                {
+                    "text": "Strongly Agree",
+                    "position": 100
+                }
+            ]
+        }, {
+            "name": "ABC",
+            "scales": [
+                {
+                    "text": "Very annoying",
+                    "position": 0
+                },
+                {
+                    "text": "Annoying",
+                    "position": 25
+                },
+                {
+                    "text": "Slightly annoying",
+                    "position": 50
+                },
+                {
+                    "text": "Perceptible but not annoying",
+                    "position": 75
+                },
+                {
+                    "text": "Imperceptible",
+                    "position": 100
+                }
+            ]
+        }, {
+            "name": "Bipolar",
+            "scales": [
+                {
+                    "text": "-50",
+                    "position": 0
+                },
+                {
+                    "text": "0",
+                    "position": 50
+                },
+                {
+                    "text": "50",
+                    "position": 100
+                }
+            ]
+        }, {
+            "name": "ACR",
+            "scales": [
+                {
+                    "text": "Bad",
+                    "position": 0
+                },
+                {
+                    "text": "Poor",
+                    "position": 25
+                },
+                {
+                    "text": "Fair",
+                    "position": 50
+                },
+                {
+                    "text": "Good",
+                    "position": 75
+                },
+                {
+                    "text": "Excellent",
+                    "position": 100
+                }
+            ]
+        }, {
+            "name": "DCR",
+            "scales": [
+                {
+                    "text": "(1) Very Annoying",
+                    "position": 0
+                },
+                {
+                    "text": "(2) Annoying",
+                    "position": 25
+                },
+                {
+                    "text": "(3) Slightly Annoying",
+                    "position": 50
+                },
+                {
+                    "text": "(4) Audible but not Annoying",
+                    "position": 75
+                },
+                {
+                    "text": "(5) Inaudible",
+                    "position": 100
+                }
+            ]
+        }, {
+            "name": "CCR",
+            "scales": [
+                {
+                    "text": "Much Worse",
+                    "position": 12
+                },
+                {
+                    "text": "Worse",
+                    "position": 25
+                },
+                {
+                    "text": "Slightly Worse",
+                    "position": 38
+                },
+                {
+                    "text": "About the same",
+                    "position": 50
+                },
+                {
+                    "text": "Slightly Better",
+                    "position": 62
+                },
+                {
+                    "text": "Better",
+                    "position": 75
+                },
+                {
+                    "text": "Much Better",
+                    "position": 88
+                }
+            ]
+        }, {
+            "name": "Hedonic Category Rating Scale",
+            "scales": [
+                {
+                    "text": "Dislike Extremeley",
+                    "position": 10
+                },
+                {
+                    "text": "Dislike Very Much",
+                    "position": 20
+                },
+                {
+                    "text": "Dislike Moderate",
+                    "position": 30
+                },
+                {
+                    "text": "Dislike Slightly",
+                    "position": 40
+                },
+                {
+                    "text": "Neither like nor dislike",
+                    "position": 50
+                },
+                {
+                    "text": "Like Slightly",
+                    "position": 60
+                },
+                {
+                    "text": "Like Moderate",
+                    "position": 70
+                },
+                {
+                    "text": "Like Very Much",
+                    "position": 80
+                },
+                {
+                    "text": "Like Extremely",
+                    "position": 90
+                }
+            ]
+        }
+    ]
+}
--- a/test_create/style.css	Tue Jun 27 21:22:15 2017 +0100
+++ b/test_create/style.css	Wed Jul 05 12:22:29 2017 +0100
@@ -1,131 +1,95 @@
-div#blanket {
-    z-index: 2;
-    background-color: rgba(0, 0, 0, 0.5);
+#screenblank {
+    z-index: 1;
     width: 100%;
     height: 100%;
     position: fixed;
+    top: 0px;
     left: 0px;
+    background-color: rgba(0, 0, 0, 0.75);
+}
+#popupHolder {
+    text-align: center;
+    width: 100%;
+    height: 100%;
+    position: fixed;
     top: 0px;
+    z-index: 2;
 }
-div#popupHolder {
+.popup {
+    position: relative;
     z-index: 3;
     background-color: rgba(255, 255, 255, 1);
     width: 730px;
     height: 480px;
-    position: fixed;
+    display: inline-block;
+    text-align: left;
     border-radius: 10px;
     box-shadow: 0px 0px 50px #000;
     padding: 10px;
+    margin-top: 20px;
 }
-div#popup-title-holder {
+.popupTitle {
     width: 100%;
     height: 50px;
     font-size: 2em;
+    text-align: center;
 }
-button.popup-button {
-    width: 60px;
-    height: 27px;
-    padding: 5px;
+.popupButtons {
     position: absolute;
-    bottom: 10px;
+    bottom: 5px;
+    width: 90%;
+    margin-left: 30px;
+    display: block;
+    align-self: center;
 }
-button#popup-proceed {
-    right: 10px;
+#popupBack {
+    float: left;
 }
-button#popup-back {
-    left: 10px;
+#popupNext {
+    float: right;
 }
-div.drag-area {
-    border: 3px black dashed;
+#introdragdrop {
+    width: 100%;
+    height: 100px;
+    border: 2px dashed black;
+    font-size: 1.5em;
+    text-align: center;
+    padding-top: 30px;
+    color: grey;
 }
-div.drag-over {
-    background-color: aquamarine;
+.new-test {
+    cursor: pointer;
 }
-div.drag-dropped {
-    background-color: aqua;
+.new-test:hover {
+    font-style: italic;
 }
-div.drag-error {
-    background-color: coral
+.node {
+    padding: 10px 20px;
+    border: 2px solid black;
+    margin: 20px;
+    border-radius: 20px;
+    background-color: inherit;
 }
-div#project-drop {
-    width: 99%;
-    height: 50px;
-    margin: 10px 0px;
+.node > textarea {
+    width: 80%;
 }
-div.popup-checkbox {
-    padding: 5px;
+.node > h1,
+h2,
+h3,
+h4,
+h5 {
+    text-align: center;
 }
-div.popup-checkbox input {
-    margin: 0px 5px;
+.attribute {
+    display: inline-block;
+    margin: 0px 10px;
+    border-left: 1px solid grey;
+    border-right: 1px solid grey;
+    padding: 5px 5px;
 }
-div.popup-option-entry {
-    padding: 5px 0px;
-    border-bottom: 1px solid;
+#setupNode {
+    background-color: rgba(255, 10, 10, 0.25);
 }
-div.disabled {
-    color: rgb(100, 100, 100);
+.pageNode {
+    background-color: rgba(10, 255, 10, 0.25);
 }
-div#page-holder > div.node {
-    background-color: rgb(200, 228, 151);
-}
-div#content > div#setup {
-    background-color: coral;
-}
-div.node {
-    float: left;
-    padding: 10px;
-    border: black 2px solid;
-    border-radius: 10px;
-    margin: 10px;
-    min-width: 92%;
-    background-color: rgba(255, 255, 255, 0.5);
-}
-div.node-title {
-    float: left;
-    width: 100%;
-    font-size: 2em;
-    margin: 5px 0px;
-}
-div.node-attributes {
-    min-width: 92%;
-    float: none;
-    padding: 10px;
-}
-div.attribute {
-    float: left;
-    margin-right: 10px;
-}
-div.node-children {
-    float: left;
-    min-width: 92%;
-}
-div.node-buttons {
-    float: left;
-    min-width: 92%;
-}
-div.attribute input {
-    max-width: 100%;
-    width: 300px;
-    margin-right: 10px;
-}
-div.attribute input[type=number] {
-    width: 80px;
-}
-div.attribute input[type=radio],
-div.attribute input[type=checkbox] {
-    width: 10px;
-}
-input:disabled+label {
-    text-decoration: line-through;
-}
-div.survey-entry-attribute {
-    margin: 10px 0px;
-    border: 1px gray solid;
-    border-radius: 5px;
-    height: 40px;
-    line-height: 40px;
-    padding: 0px 10px;
-}
-div.survey-entry-attribute span {
-    margin-right: 10px;
-}
--- a/test_create/test_core.js	Tue Jun 27 21:22:15 2017 +0100
+++ b/test_create/test_core.js	Wed Jul 05 12:22:29 2017 +0100
@@ -1,2339 +1,453 @@
-var interfaceSpecs;
-var xmlHttp;
-var popupObject;
-var popupStateNodes;
-var specification;
-var convert;
-var attributeText;
-var page_lang = "en";
+/* globals document, angular, window, Promise, XMLHttpRequest, Specification, XMLSerializer, Blob, DOMParser, FileReader, $*/
+function get(url) {
+    // Return a new promise.
+    return new Promise(function (resolve, reject) {
+        // Do the usual XHR stuff
+        var req = new XMLHttpRequest();
+        req.open('GET', url);
 
-// Firefox does not have an XMLDocument.prototype.getElementsByName
-// and there is no searchAll style command, this custom function will
-// search all children recusrively for the name. Used for XSD where all
-// element nodes must have a name and therefore can pull the schema node
-XMLDocument.prototype.getAllElementsByName = function (name) {
-    name = String(name);
-    var selected = this.documentElement.getAllElementsByName(name);
-    return selected;
+        req.onload = function () {
+            // This is called even on 404 etc
+            // so check the status
+            if (req.status == 200) {
+                // Resolve the promise with the response text
+                resolve(req.response);
+            } else {
+                // Otherwise reject with the status text
+                // which will hopefully be a meaningful error
+                reject(Error(req.statusText));
+            }
+        };
+
+        // Handle network errors
+        req.onerror = function () {
+            reject(Error("Network Error"));
+        };
+
+        // Make the request
+        req.send();
+    });
 }
 
-Element.prototype.getAllElementsByName = function (name) {
-    name = String(name);
-    var selected = [];
-    var node = this.firstElementChild;
-    while (node != null) {
-        if (node.getAttribute('name') == name) {
-            selected.push(node);
-        }
-        if (node.childElementCount > 0) {
-            selected = selected.concat(node.getAllElementsByName(name));
-        }
-        node = node.nextElementSibling;
-    }
-    return selected;
+var AngularInterface = angular.module("creator", []);
+
+var specification = new Specification();
+
+window.onload = function () {
+    // Get the test interface specifications
+    $(function () {
+        $('[data-toggle="popover"]').popover();
+    });
+};
+
+function handleFiles(event) {
+    var s = angular.element(event.currentTarget).scope();
+    s.handleFiles(event);
+    s.$apply();
 }
 
-XMLDocument.prototype.getAllElementsByTagName = function (name) {
-    name = String(name);
-    var selected = this.documentElement.getAllElementsByTagName(name);
-    return selected;
-}
+AngularInterface.controller("view", ['$scope', '$element', '$window', function ($s, $e, $w) {
+    $s.popupVisible = true;
+    $s.testSpecifications = {};
 
-Element.prototype.getAllElementsByTagName = function (name) {
-    name = String(name);
-    var selected = [];
-    var node = this.firstElementChild;
-    while (node != null) {
-        if (node.nodeName == name) {
-            selected.push(node);
+    (function () {
+        new Promise(function (resolve, reject) {
+            var xml = new XMLHttpRequest();
+            xml.open("GET", "test_create/interfaces/specifications.json");
+            xml.onload = function () {
+                if (xml.status === 200) {
+                    resolve(xml.responseText);
+                    return;
+                }
+                reject(xml.status);
+            };
+            xml.onerror = function () {
+                reject(new Error("Network Error"));
+            };
+            xml.send();
+        }).then(JSON.parse).then(function (data) {
+            $s.testSpecifications = data;
+            $s.$apply();
+        });
+    })();
+
+    $s.showPopup = function () {
+        $s.popupVisible = true;
+    };
+    $s.hidePopup = function () {
+        $s.popupVisible = false;
+    };
+    $s.globalSchema = undefined;
+    get("xml/test-schema.xsd").then(function (text) {
+        specification.processSchema(text);
+        $s.globalSchema = specification.getSchema();
+    });
+    $s.specification = specification;
+    $s.selectedTestPrototype = undefined;
+    $s.setTestPrototype = function (obj) {
+        $s.selectedTestPrototype = obj;
+        $w.specification.interface = obj.interface;
+    }
+
+    $s.addPage = function () {
+        $s.specification.createNewPage();
+    };
+
+    $s.removePage = function (page) {
+        var index = $s.specification.pages.findIndex(function (a) {
+            return a == page;
+        });
+        if (index === -1) {
+            throw ("Invalid Page");
         }
-        if (node.childElementCount > 0) {
-            selected = selected.concat(node.getAllElementsByTagName(name));
+        $s.specification.pages.splice(index, 1);
+    };
+
+    $s.exportXML = function () {
+        var s = new XMLSerializer();
+        var doc = specification.encode();
+        var xmlstr = s.serializeToString(doc);
+        var bb = new Blob([s.serializeToString(doc)], {
+            type: 'application/xml'
+        });
+        var dnlk = window.URL.createObjectURL(bb);
+        var a = document.createElement("a");
+        a.href = dnlk;
+        a.download = "test.xml";
+        a.click();
+        window.URL.revokeObjectURL(dnlk);
+    };
+    $s.validated = false;
+    $s.showValidationMessages = false;
+    $s.validate = function () {
+        var s = new XMLSerializer();
+        var Module = {
+            xml: s.serializeToString(specification.encode()),
+            schema: specification.getSchemaString(),
+            arguments: ["--noout", "--schema", 'test-schema.xsd', 'document.xml']
+        };
+        var xmllint = validateXML(Module);
+        console.log(xmllint);
+        if (xmllint != 'document.xml validates\n') {
+            $s.validated = false;
+            var list = $e[0].querySelector("#validation-error-list");
+            while (list.firstChild) {
+                list.removeChild(list.firstChild);
+            }
+            var errors = xmllint.split('\n');
+            errors = errors.slice(0, errors.length - 2);
+            errors.forEach(function (str) {
+                var li = document.createElement("li");
+                li.textContent = str;
+                list.appendChild(li);
+            });
+        } else {
+            $s.validated = true;
         }
-        node = node.nextElementSibling;
+        $s.showValidationMessages = true;
     }
-    return selected;
-}
+    $s.hideValidationMessages = function () {
+        $s.showValidationMessages = false;
+    }
+}]);
 
-// Firefox does not have an XMLDocument.prototype.getElementsByName
-if (typeof XMLDocument.prototype.getElementsByName != "function") {
-    XMLDocument.prototype.getElementsByName = function (name) {
-        name = String(name);
-        var node = this.documentElement.firstElementChild;
-        var selected = [];
-        while (node != null) {
-            if (node.getAttribute('name') == name) {
-                selected.push(node);
+AngularInterface.controller("introduction", ['$scope', '$element', '$window', function ($s, $e, $w) {
+    $s.state = 0;
+    $s.next = function () {
+        $s.state++;
+        if ($s.state > 1 || $s.file) {
+            $s.hidePopup();
+        }
+    };
+    $s.back = function () {
+        $s.state--;
+    };
+    $s.mouseover = function (name) {
+        var obj = $s.testSpecifications.interfaces.find(function (i) {
+            return i.name == name;
+        });
+        if (obj) {
+            $s.description = obj.description.en;
+        }
+    };
+    $s.initialise = function (name) {
+        var obj = $s.testSpecifications.interfaces.find(function (i) {
+            return i.name == name;
+        });
+        if (obj === undefined) {
+            throw ("Cannot find specification");
+        }
+        $s.setTestPrototype(obj);
+    };
+    // Get the test interface specifications
+    $s.file = undefined;
+    $s.description = "";
+
+    $s.handleFiles = function ($event) {
+        $s.file = $event.currentTarget.files[0];
+        var r = new FileReader();
+        r.onload = function () {
+            var p = new DOMParser();
+            specification.decode(p.parseFromString(r.result, "text/xml"));
+            $s.$apply();
+        };
+        r.readAsText($s.file);
+    };
+}]);
+
+AngularInterface.controller("setup", ['$scope', '$element', '$window', function ($s, $e, $w) {
+    function initialise() {
+        if ($s.globalSchema) {
+            $s.schema = $s.globalSchema.querySelector("[name=setup]");
+        }
+    }
+    $s.schema = undefined;
+    $s.attributes = [];
+
+    $s.$watch("globalSchema", initialise);
+    $s.$watch("specification.metrics.enabled.length", function () {
+        var metricsNode = document.getElementById("metricsNode");
+        if (!$s.specification.metrics) {
+            return;
+        }
+        metricsNode.querySelectorAll("input").forEach(function (DOM) {
+            DOM.checked = false;
+        });
+        $s.specification.metrics.enabled.forEach(function (metric) {
+            var DOM = metricsNode.querySelector("[value=" + metric + "]");
+            if (DOM) {
+                DOM.checked = true;
             }
-            node = node.nextElementSibling;
-        }
-        return selected;
-    }
-}
+        });
+    });
 
-window.onload = function () {
-    specification = new Specification();
-    convert = new SpecificationToHTML();
-    xmlHttp = new XMLHttpRequest();
-    xmlHttp.open("GET", "test_create/interface-specs.xml", true);
-    xmlHttp.onload = function () {
-        var parse = new DOMParser();
-        interfaceSpecs = parse.parseFromString(xmlHttp.response, 'text/xml');
-        buildPage();
-        popupObject.postNode(popupStateNodes.state[0])
-    }
-    xmlHttp.send();
-
-    var xsdGet = new XMLHttpRequest();
-    xsdGet.open("GET", "xml/test-schema.xsd", true);
-    xsdGet.onload = function () {
-        var parse = new DOMParser();
-        specification.schema = parse.parseFromString(xsdGet.response, 'text/xml');;
-    }
-    xsdGet.send();
-
-    var jsonAttribute = new XMLHttpRequest();
-    jsonAttribute.open("GET", "test_create/attributes.json", true);
-    jsonAttribute.onload = function () {
-        attributeText = JSON.parse(jsonAttribute.response)
-    }
-    jsonAttribute.send();
-}
-
-function buildPage() {
-    popupObject = new function () {
-        this.object = document.getElementById("popupHolder");
-        this.blanket = document.getElementById("blanket");
-
-        this.popupTitle = document.createElement("div");
-        this.popupTitle.id = "popup-title-holder";
-        this.popupTitle.align = "center";
-        this.titleDOM = document.createElement("span");
-        this.titleDOM.id = "popup-title";
-        this.popupTitle.appendChild(this.titleDOM);
-        this.object.appendChild(this.popupTitle);
-
-        this.popupContent = document.createElement("div");
-        this.popupContent.id = "popup-content";
-        this.object.appendChild(this.popupContent);
-
-        this.proceedButton = document.createElement("button");
-        this.proceedButton.id = "popup-proceed";
-        this.proceedButton.className = "popup-button";
-        this.proceedButton.textContent = "Next";
-        this.proceedButton.onclick = function () {
-            popupObject.popupContent.innerHTML = null;
-            if (typeof popupObject.shownObject.continue == "function") {
-                popupObject.shownObject.continue();
-            } else {
-                popupObject.hide();
+    $s.enableMetric = function ($event) {
+        var metric = $event.currentTarget.value;
+        var index = specification.metrics.enabled.findIndex(function (a) {
+            return a == metric;
+        });
+        if ($event.currentTarget.checked) {
+            if (index == -1) {
+                specification.metrics.enabled.push(metric);
             }
-        };
-        this.object.appendChild(this.proceedButton);
-
-        this.backButton = document.createElement("button");
-        this.backButton.id = "popup-back";
-        this.backButton.className = "popup-button";
-        this.backButton.textContent = "Back";
-        this.backButton.onclick = function () {
-            popupObject.popupContent.innerHTML = null;
-            popupObject.shownObject.back();
-        };
-        this.object.appendChild(this.backButton);
-
-        this.shownObject;
-
-        this.resize = function () {
-            var w = window.innerWidth;
-            var h = window.innerHeight;
-            this.object.style.left = Math.floor((w - 750) / 2) + 'px';
-            this.object.style.top = Math.floor((h - 500) / 2) + 'px';
-        }
-
-        this.show = function () {
-            this.object.style.visibility = "visible";
-            this.blanket.style.visibility = "visible";
-            if (typeof this.shownObject.back == "function") {
-                this.backButton.style.visibility = "visible";
-            } else {
-                this.backButton.style.visibility = "hidden";
+        } else {
+            if (index >= 0) {
+                specification.metrics.enabled.splice(index, 1);
             }
         }
-
-        this.hide = function () {
-            this.object.style.visibility = "hidden";
-            this.blanket.style.visibility = "hidden";
-            this.backButton.style.visibility = "hidden";
-        }
-
-        this.postNode = function (postObject) {
-            //Passed object must have the following:
-            // Title: text to show in the title
-            // Content: HTML DOM to show on the page
-            // On complete this HTML DOM is destroyed so make sure it is referenced elsewhere for processing
-            this.titleDOM.textContent = postObject.title;
-            this.popupContent.appendChild(postObject.content);
-            this.shownObject = postObject;
-            if (typeof this.shownObject.back == "function") {
-                this.backButton.style.visibility = "visible";
-            } else {
-                this.backButton.style.visibility = "hidden";
-            }
-            if (typeof this.shownObject.continue == "function") {
-                this.proceedButton.textContent = "Next";
-            } else {
-                this.proceedButton.textContent = "Finish";
-            }
-            this.show();
-        }
-
-        this.resize();
-        this.hide();
     };
 
-    popupStateNodes = new function () {
-        // This defines the several popup states wanted
-        this.state = [];
-        this.state[0] = new function () {
-            this.title = "Welcome";
-            this.content = document.createElement("div");
-            this.content.id = "state-0";
-            var span = document.createElement("span");
-            span.textContent = "Welcome to the WAET test creator tool. This will allow you to create a new test from scratch to suit your testing needs. If you wish to update a test file, please drag and drop the XML document into the area below for processing, otherwise press 'Next' to start a new test. This tool generates files for the WAET 1.2.0 version."
-            this.content.appendChild(span);
-            this.dragArea = document.createElement("div");
-            this.dragArea.className = "drag-area";
-            this.dragArea.id = "project-drop";
-            this.content.appendChild(this.dragArea);
+    $s.configure = function () {}
 
-            this.dragArea.addEventListener('dragover', function (e) {
-                e.stopPropagation();
-                e.preventDefault();
-                e.dataTransfer.dropEffect = 'copy';
-                e.currentTarget.className = "drag-area drag-over";
+    $s.$watch("selectedTestPrototype", $s.configure);
+}]);
+
+AngularInterface.controller("survey", ['$scope', '$element', '$window', function ($s, $e, $w) {
+    $s.addSurveyEntry = function () {
+        $s.survey.addOption();
+    };
+    $s.removeSurveyEntry = function (entry) {
+        var index = $s.survey.options.findIndex(function (a) {
+            return a == entry;
+        });
+        if (index === -1) {
+            throw ("Invalid Entry");
+        }
+        $s.survey.options.splice(index, 1);
+    };
+}]);
+
+AngularInterface.controller("surveyOption", ['$scope', '$element', '$window', function ($s, $e, $w) {
+
+    $s.removeOption = function (option) {
+        var index = $s.opt.options.findIndex(function (a) {
+            return a == option;
+        });
+        if (index === -1) {
+            throw ("Invalid option");
+        }
+        $s.opt.options.splice(index, 1);
+    };
+    $s.addOption = function () {
+        $s.opt.options.push({
+            name: "",
+            text: ""
+        });
+    };
+
+    $s.addCondition = function () {
+        $s.opt.conditions.push({
+            check: "equals",
+            value: "",
+            jumpToOnPass: undefined,
+            jumpToOnFail: undefined
+        });
+    };
+
+    $s.removeCondition = function (condition) {
+        var index = $s.opt.conditions.findIndex(function (c) {
+            return c == condition;
+        });
+        if (index === -1) {
+            throw ("Invalid Condition");
+        }
+        $s.opt.conditions.splice(index, 1);
+    };
+}]);
+
+AngularInterface.controller("interfaceNode", ['$scope', '$element', '$window', function ($s, $e, $w) {
+    $s.$watch("interface.options.length", function () {
+        if (!$s.interface || !$s.interface.options) {
+            return;
+        }
+        var options = $e[0].querySelector(".interfaceOptions").querySelectorAll(".attribute");
+        options.forEach(function (option) {
+            var name = option.getAttribute("name");
+            var index = $s.interface.options.findIndex(function (io) {
+                return io.name == name;
             });
+            option.querySelector("input").checked = (index >= 0);
+            if (name == "scalerange" && index >= 0) {
+                option.querySelector("[name=min]").value = $s.interface.options[index].min;
+                option.querySelector("[name=max]").value = $s.interface.options[index].max;
+            }
+        });
+    });
+    $s.enableInterfaceOption = function ($event) {
+        var name = $event.currentTarget.parentElement.getAttribute("name");
+        var type = $event.currentTarget.parentElement.getAttribute("type");
+        var index = $s.interface.options.findIndex(function (io) {
+            return io.name == name;
+        });
+        if (index == -1 && $event.currentTarget.checked) {
+            var obj = $s.interface.options.push({
+                name: name,
+                type: type
+            });
+            if (name == "scalerange") {
+                obj.min = $event.currentTarget.parentElement.querySelector("[name=min]").value;
+                obj.max = $event.currentTarget.parentElement.querySelector("[name=max]").value;
+            }
+        } else if (index >= 0 && !$event.currentTarget.checked) {
+            $s.interface.options.splice(index, 1);
+        }
+    };
+    $s.scales = [];
+    $s.removeScale = function (scale) {
+        var index = $s.interface.scales.findIndex(function (s) {
+            return s == scale;
+        });
+        if (index >= 0) {
+            $s.interface.scales.splice(index, 1);
+        }
+    };
+    $s.addScale = function () {
+        $s.interface.scales.push({
+            position: undefined,
+            text: undefined
+        });
+    };
+    $s.clearScales = function () {
+        $s.interface.scales = [];
+    };
+    $s.useScales = function (scale) {
+        $s.clearScales();
+        scale.scales.forEach(function (s) {
+            $s.interface.scales.push(s);
+        });
+        $s.selectedScale = scale.name;
+    };
+    $s.selectedScale = undefined;
 
-            this.dragArea.addEventListener('dragexit', function (e) {
-                e.stopPropagation();
-                e.preventDefault();
-                e.dataTransfer.dropEffect = 'copy';
-                e.currentTarget.className = "drag-area";
+    $s.configure = function () {
+        if ($s.selectedTestPrototype === undefined) {
+            return;
+        }
+        if ($s.selectedTestPrototype.checks && $s.selectedTestPrototype.checks.length >= 1) {
+            $s.selectedTestPrototype.checks.forEach(function (entry) {
+                var dom = $e[0].querySelector("[name=\"" + entry.name + "\"] input");
+                if (entry.support == "none") {
+                    dom.checked = false;
+                    dom.disabled = true;
+                }
             });
-
-            this.dragArea.addEventListener('drop', function (e) {
-                e.stopPropagation();
-                e.preventDefault();
-                e.currentTarget.className = "drag-area drag-dropped";
-                var files = e.dataTransfer.files[0];
-                var reader = new FileReader();
-                reader.onload = function (decoded) {
-                    var parse = new DOMParser();
-                    specification.decode(parse.parseFromString(decoded.target.result, 'text/xml'));
-                    popupObject.hide();
-                    popupObject.popupContent.innerHTML = null;
-                    convert.convert(document.getElementById('content'));
+        }
+        if ($s.selectedTestPrototype.show && $s.selectedTestPrototype.show.length >= 1) {
+            $s.selectedTestPrototype.show.forEach(function (entry) {
+                var dom = $e[0].querySelector("[name=\"" + entry.name + "\"] input");
+                if (entry.support == "none") {
+                    dom.checked = false;
+                    dom.disabled = true;
                 }
-                reader.readAsText(files);
             });
-
-
-            this.continue = function () {
-                popupObject.postNode(popupStateNodes.state[1]);
+        }
+        if ($s.interface !== specification.interfaces) {
+            // Page specific interface nodes
+            if ($s.selectedTestPrototype.hasScales !== undefined && ($s.selectedTestPrototype.hasScales == "false" || $s.selectedTestPrototype.hasScales == false)) {
+                var elem = $e[0].querySelector("[name=\"scale-selection\"]")
+                elem.style.visibility = "hidden";
+                elem.style.height = "0px";
+            }
+            if ($s.selectedTestPrototype.scales && $s.selectedTestPrototype.show.length >= 1) {
+                $s.scales = [];
+                $s.selectedTestPrototype.scales.forEach(function (scalename) {
+                    var obj = $s.testSpecifications.scales.find(function (a) {
+                        return a.name == scalename;
+                    });
+                    $s.scales.push(obj);
+                });
+                if ($s.selectedTestPrototype.scales.includes($s.selectedScale) == false) {
+                    $s.clearScales();
+                }
+                if ($s.scales.length == 1) {
+                    $s.clearScales();
+                    $s.useScales($s.scales[0]);
+                }
+            } else {
+                $s.scales = $s.testSpecifications.scales;
             }
         }
-        this.state[1] = new function () {
-            this.title = "Select your interface";
-            this.content = document.createElement("div");
-            this.content.id = "state-1";
-            var spnH = document.createElement('div');
-            var span = document.createElement("span");
-            span.textContent = "Please select your interface from the list shown below. This will define the various options which are available. This can later be changed.";
-            spnH.appendChild(span);
-            this.content.appendChild(spnH);
-            this.select = document.createElement("select");
-            this.content.appendChild(this.select);
-            this.description = document.createElement("p");
-            this.content.appendChild(this.description);
-            this.testsXML = interfaceSpecs.getElementsByTagName('tests')[0].getElementsByTagName('test');
-            for (var i = 0; i < this.testsXML.length; i++) {
-                var option = document.createElement('option');
-                option.value = this.testsXML[i].getAttribute('name');
-                option.textContent = this.testsXML[i].getAttribute('name');
-                this.select.appendChild(option);
-            }
-            this.handleEvent = function (event) {
-                var testXML = interfaceSpecs.getElementsByTagName("tests")[0].getAllElementsByName(this.select.value)[0];
-                var descriptors = testXML.getAllElementsByTagName("description");
-                this.description.textContent = "";
-                for (var i = 0; i < descriptors.length; i++) {
-                    if (descriptors[i].getAttribute("lang") == page_lang) {
-                        this.description.textContent = descriptors[i].textContent;
-                    }
-                }
-            }
-            this.select.addEventListener("change", this);
-            this.handleEvent();
-            this.continue = function () {
-                var testXML = interfaceSpecs.getElementsByTagName("tests")[0].getAllElementsByName(this.select.value)[0];
-                specification.interface = testXML.getAttribute("interface");
-                if (specification.interfaces == null) {
-                    specification.interfaces = new specification.interfaceNode(specification);
-                }
-                if (specification.metrics == null) {
-                    specification.metrics = new specification.metricNode();
-                }
-                popupStateNodes.state[2].generate();
-                popupObject.postNode(popupStateNodes.state[2]);
-            }
-            this.back = function () {
-                popupObject.postNode(popupStateNodes.state[0]);
-            }
+    };
+
+    $s.$watch("selectedTestPrototype", $s.configure);
+    $s.configure();
+}]);
+AngularInterface.controller("page", ['$scope', '$element', '$window', function ($s, $e, $w) {
+    $s.addInterface = function () {
+        $s.page.addInterface();
+    };
+    $s.removeInterface = function (node) {
+        var index = $s.page.interfaces.findIndex(function (a) {
+            return a == node;
+        });
+        if (index === -1) {
+            throw ("Invalid node");
         }
-        this.state[2] = new function () {
-            this.title = "Test Checks & Restrictions";
-            this.content = document.createElement("div");
-            this.content.id = "state-1";
-            var spnH = document.createElement('div');
-            var span = document.createElement("span");
-            span.textContent = "Select your test checks and restrictions. Greyed out items are fixed by the test/interface and cannot be changed";
-            spnH.appendChild(span);
-            this.content.appendChild(spnH);
-            var holder = document.createElement("div");
-            this.options = [];
-            this.testXML = null;
-            this.interfaceXML = null;
-            this.dynamicContent = document.createElement("div");
-            this.content.appendChild(this.dynamicContent);
-            this.generate = function () {
-                this.options = [];
-                this.dynamicContent.innerHTML = null;
-                var interfaceName = popupStateNodes.state[1].select.value;
-                this.checkText = interfaceSpecs.getElementsByTagName("global")[0].getAllElementsByTagName("checks")[0];
-                this.testXML = interfaceSpecs.getElementsByTagName("tests")[0].getAllElementsByName(interfaceName)[0];
-                this.interfaceXML = interfaceSpecs.getAllElementsByTagName("interfaces")[0].getAllElementsByName(this.testXML.getAttribute("interface"))[0].getAllElementsByTagName("checks")[0];
-                this.testXML = this.testXML.getAllElementsByTagName("checks");
-                var interfaceXMLChildren = this.interfaceXML.getElementsByTagName('entry');
-                for (var i = 0; i < interfaceXMLChildren.length; i++) {
-                    var interfaceNode = interfaceXMLChildren[i];
-                    var checkName = interfaceNode.getAttribute('name');
-                    var testNode
-                    if (this.testXML.length > 0) {
-                        testNode = this.testXML[0].getAllElementsByName(checkName);
-                        if (testNode.length != 0) {
-                            testNode = testNode[0];
-                        } else {
-                            testNode = undefined;
-                        }
-                    } else {
-                        testNode = undefined;
-                    }
-                    var obj = {
-                        root: document.createElement("div"),
-                        text: document.createElement("label"),
-                        input: document.createElement("input"),
-                        parent: this,
-                        name: checkName,
-                        handleEvent: function (event) {
-                            if (this.input.checked) {
-                                // Add to specification.interfaces.option
-                                var included = specification.interfaces.options.find(function (element, index, array) {
-                                    if (element.name == this.name) {
-                                        return true;
-                                    } else {
-                                        return false;
-                                    }
-                                }, this);
-                                if (included == null) {
-                                    specification.interfaces.options.push({
-                                        type: "check",
-                                        name: this.name
-                                    });
-                                }
-                            } else {
-                                // Remove from specification.interfaces.option
-                                var position = specification.interfaces.options.findIndex(function (element, index, array) {
-                                    if (element.name == this.name) {
-                                        return true;
-                                    } else {
-                                        return false;
-                                    }
-                                }, this);
-                                if (position >= 0) {
-                                    specification.interfaces.options.splice(position, 1);
-                                }
-                            }
-                        }
-                    }
+        $s.page.interfaces.splice(index, 1);
+    };
 
-                    obj.input.addEventListener("click", obj);
-                    obj.root.className = "popup-checkbox";
-                    obj.input.type = "checkbox";
-                    obj.input.setAttribute('id', checkName);
-                    obj.text.setAttribute("for", checkName);
-                    obj.text.textContent = this.checkText.getAllElementsByName(checkName)[0].textContent;
-                    obj.root.appendChild(obj.input);
-                    obj.root.appendChild(obj.text);
-                    if (testNode != undefined) {
-                        if (testNode.getAttribute('default') == 'on') {
-                            obj.input.checked = true;
-                        }
-                        if (testNode.getAttribute('support') == "none") {
-                            obj.input.disabled = true;
-                            obj.input.checked = false;
-                            obj.root.className = "popup-checkbox disabled";
-                        } else if (interfaceNode.getAttribute('support') == "mandatory") {
-                            obj.input.disabled = true;
-                            obj.input.checked = true;
-                            obj.root.className = "popup-checkbox disabled";
-                        }
-                    } else {
-                        if (interfaceNode.getAttribute('default') == 'on') {
-                            obj.input.checked = true;
-                        }
-                        if (interfaceNode.getAttribute('support') == "none") {
-                            obj.input.disabled = true;
-                            obj.input.checked = false;
-                            obj.root.className = "popup-checkbox disabled";
-                        } else if (interfaceNode.getAttribute('support') == "mandatory") {
-                            obj.input.disabled = true;
-                            obj.input.checked = true;
-                            obj.root.className = "popup-checkbox disabled";
-                        }
-                    }
-                    var included = specification.interfaces.options.find(function (element, index, array) {
-                        if (element.name == this.name) {
-                            return true;
-                        } else {
-                            return false;
-                        }
-                    }, obj);
-                    if (included != undefined) {
-                        obj.input.checked = true;
-                    }
-                    obj.handleEvent();
-                    this.options.push(obj);
-                    this.dynamicContent.appendChild(obj.root);
-                }
-            }
-            this.continue = function () {
-                popupStateNodes.state[3].generate();
-                popupObject.postNode(popupStateNodes.state[3]);
-            }
-            this.back = function () {
-                popupObject.postNode(popupStateNodes.state[1]);
-            }
+    $s.addCommentQuestion = function () {
+        $s.page.addCommentQuestion();
+    };
+    $s.removeCommentQuestion = function (node) {
+        var index = $s.page.commentQuestions.findIndex(function (a) {
+            return a == node;
+        });
+        if (index === -1) {
+            throw ("Invalid node");
         }
-        this.state[3] = new function () {
-            this.title = "Test Metrics";
-            this.content = document.createElement("div");
-            this.content.id = "state-1";
-            var spnH = document.createElement('div');
-            var span = document.createElement("span");
-            span.textContent = "Select which data points to include in the exported results XML. Some of this is required for certain post script analysis. See the documentation for further details";
-            spnH.appendChild(span);
-            this.content.appendChild(spnH);
-            this.options = [];
-            this.checkText;
-            this.testXML;
-            this.interfaceXML;
-            this.dynamicContent = document.createElement("div");
-            this.content.appendChild(this.dynamicContent);
-            this.generate = function () {
-                this.options = [];
-                this.dynamicContent.innerHTML = null;
-                var interfaceName = popupStateNodes.state[1].select.value;
-                this.checkText = interfaceSpecs.getElementsByTagName("global")[0].getAllElementsByTagName("metrics")[0];
-                this.testXML = interfaceSpecs.getElementsByTagName("tests")[0].getAllElementsByName(interfaceName)[0];
-                this.interfaceXML = interfaceSpecs.getAllElementsByTagName("interfaces")[0].getAllElementsByName(this.testXML.getAttribute("interface"))[0].getAllElementsByTagName("metrics")[0];
-                this.testXML = this.testXML.getAllElementsByTagName("metrics");
-                var interfaceXMLChildren = this.interfaceXML.getElementsByTagName('entry');
-                for (var i = 0; i < interfaceXMLChildren.length; i++) {
-                    var interfaceNode = interfaceXMLChildren[i];
-                    var checkName = interfaceNode.getAttribute('name');
-                    var testNode
-                    if (this.testXML.length > 0) {
-                        testNode = this.testXML[0].getAllElementsByName(checkName);
-                        if (testNode.length != 0) {
-                            testNode = testNode[0];
-                        } else {
-                            testNode = undefined;
-                        }
-                    } else {
-                        testNode = undefined;
-                    }
-                    var obj = {
-                        root: document.createElement("div"),
-                        text: document.createElement("label"),
-                        input: document.createElement("input"),
-                        parent: this,
-                        name: checkName,
-                        handleEvent: function (event) {
-                            if (this.input.checked) {
-                                // Add to specification.interfaces.option
-                                var included = specification.metrics.enabled.find(function (element, index, array) {
-                                    if (element == this.name) {
-                                        return true;
-                                    } else {
-                                        return false;
-                                    }
-                                }, this);
-                                if (included == null) {
-                                    specification.metrics.enabled.push(this.name);
-                                }
-                            } else {
-                                // Remove from specification.interfaces.option
-                                var position = specification.metrics.enabled.findIndex(function (element, index, array) {
-                                    if (element == this.name) {
-                                        return true;
-                                    } else {
-                                        return false;
-                                    }
-                                }, this);
-                                if (position >= 0) {
-                                    specification.metrics.enabled.splice(position, 1);
-                                }
-                            }
-                        }
-                    }
-
-                    obj.input.addEventListener("click", obj);
-                    obj.root.className = "popup-checkbox";
-                    obj.input.type = "checkbox";
-                    obj.input.setAttribute('id', checkName);
-                    obj.text.setAttribute("for", checkName);
-                    obj.text.textContent = this.checkText.getAllElementsByName(checkName)[0].textContent;
-                    obj.root.appendChild(obj.input);
-                    obj.root.appendChild(obj.text);
-                    if (testNode != undefined) {
-                        if (testNode.getAttribute('default') == 'on') {
-                            obj.input.checked = true;
-                        }
-                        if (testNode.getAttribute('support') == "none") {
-                            obj.input.disabled = true;
-                            obj.input.checked = false;
-                            obj.root.className = "popup-checkbox disabled";
-                        } else if (interfaceNode.getAttribute('support') == "mandatory") {
-                            obj.input.disabled = true;
-                            obj.input.checked = true;
-                            obj.root.className = "popup-checkbox disabled";
-                        }
-                    } else {
-                        if (interfaceNode.getAttribute('default') == 'on') {
-                            obj.input.checked = true;
-                        }
-                        if (interfaceNode.getAttribute('support') == "none") {
-                            obj.input.disabled = true;
-                            obj.input.checked = false;
-                            obj.root.className = "popup-checkbox disabled";
-                        } else if (interfaceNode.getAttribute('support') == "mandatory") {
-                            obj.input.disabled = true;
-                            obj.input.checked = true;
-                            obj.root.className = "popup-checkbox disabled";
-                        }
-                    }
-                    var included = specification.metrics.enabled.find(function (element, index, array) {
-                        if (element == this.name) {
-                            return true;
-                        } else {
-                            return false;
-                        }
-                    }, obj);
-                    obj.handleEvent();
-                    if (included != undefined) {
-                        obj.input.checked = true;
-                    }
-                    this.options.push(obj);
-                    this.dynamicContent.appendChild(obj.root);
-                }
-            }
-            this.continue = function () {
-                popupStateNodes.state[4].generate();
-                popupObject.postNode(popupStateNodes.state[4]);
-            }
-            this.back = function () {
-                popupObject.postNode(popupStateNodes.state[2]);
-            }
+        $s.page.commentQuestions.splice(index, 1);
+    };
+    $s.addAudioElement = function () {
+        $s.page.addAudioElement();
+    };
+    $s.removeAudioElement = function (element) {
+        var index = $s.page.audioElements.findIndex(function (a) {
+            return a == element;
+        });
+        if (index === -1) {
+            throw ("Invalid node");
         }
-        this.state[4] = new function () {
-            this.title = "Test Visuals";
-            this.content = document.createElement("div");
-            this.content.id = "state-1";
-            var spnH = document.createElement('div');
-            var span = document.createElement("span");
-            span.textContent = "You can display extra visual content with your interface for the test user to interact with. Select from the available options below. Greyed out options are unavailable for your selected interface";
-            spnH.appendChild(span);
-            this.content.appendChild(spnH);
-            this.options = [];
-            this.checkText;
-            this.testXML;
-            this.interfaceXML;
-            this.dynamicContent = document.createElement("div");
-            this.content.appendChild(this.dynamicContent);
-            this.generate = function () {
-                this.options = [];
-                this.dynamicContent.innerHTML = null;
-                var interfaceName = popupStateNodes.state[1].select.value;
-                this.checkText = interfaceSpecs.getElementsByTagName("global")[0].getAllElementsByTagName("show")[0];
-                this.testXML = interfaceSpecs.getElementsByTagName("tests")[0].getAllElementsByName(interfaceName)[0];
-                this.interfaceXML = interfaceSpecs.getAllElementsByTagName("interfaces")[0].getAllElementsByName(this.testXML.getAttribute("interface"))[0].getAllElementsByTagName("show")[0];
-                this.testXML = this.testXML.getAllElementsByTagName("show");
-                var interfaceXMLChildren = this.interfaceXML.getElementsByTagName('entry');
-                for (var i = 0; i < interfaceXMLChildren.length; i++) {
-                    var interfaceNode = interfaceXMLChildren[i];
-                    var checkName = interfaceNode.getAttribute('name');
-                    var testNode
-                    if (this.testXML.length > 0) {
-                        testNode = this.testXML[0].getAllElementsByName(checkName);
-                        if (testNode.length != 0) {
-                            testNode = testNode[0];
-                        } else {
-                            testNode = undefined;
-                        }
-                    } else {
-                        testNode = undefined;
-                    }
-                    var obj = {
-                        root: document.createElement("div"),
-                        text: document.createElement("label"),
-                        input: document.createElement("input"),
-                        parent: this,
-                        name: checkName,
-                        handleEvent: function (event) {
-                            if (this.input.checked) {
-                                // Add to specification.interfaces.option
-                                var included = specification.interfaces.options.find(function (element, index, array) {
-                                    if (element.name == this.name) {
-                                        return true;
-                                    } else {
-                                        return false;
-                                    }
-                                }, this);
-                                if (included == null) {
-                                    specification.interfaces.options.push({
-                                        type: "show",
-                                        name: this.name
-                                    });
-                                }
-                            } else {
-                                // Remove from specification.interfaces.option
-                                var position = specification.interfaces.options.findIndex(function (element, index, array) {
-                                    if (element.name == this.name) {
-                                        return true;
-                                    } else {
-                                        return false;
-                                    }
-                                }, this);
-                                if (position >= 0) {
-                                    specification.interfaces.options.splice(position, 1);
-                                }
-                            }
-                        }
-                    }
-
-                    obj.input.addEventListener("click", obj);
-                    obj.root.className = "popup-checkbox";
-                    obj.input.type = "checkbox";
-                    obj.input.setAttribute('id', checkName);
-                    obj.text.setAttribute("for", checkName);
-                    obj.text.textContent = this.checkText.getAllElementsByName(checkName)[0].textContent;
-                    obj.root.appendChild(obj.input);
-                    obj.root.appendChild(obj.text);
-                    if (testNode != undefined) {
-                        if (testNode.getAttribute('default') == 'on') {
-                            obj.input.checked = true;
-                        }
-                        if (testNode.getAttribute('support') == "none") {
-                            obj.input.disabled = true;
-                            obj.input.checked = false;
-                            obj.root.className = "popup-checkbox disabled";
-                        } else if (interfaceNode.getAttribute('support') == "mandatory") {
-                            obj.input.disabled = true;
-                            obj.input.checked = true;
-                            obj.root.className = "popup-checkbox disabled";
-                        }
-                    } else {
-                        if (interfaceNode.getAttribute('default') == 'on') {
-                            obj.input.checked = true;
-                        }
-                        if (interfaceNode.getAttribute('support') == "none") {
-                            obj.input.disabled = true;
-                            obj.input.checked = false;
-                            obj.root.className = "popup-checkbox disabled";
-                        } else if (interfaceNode.getAttribute('support') == "mandatory") {
-                            obj.input.disabled = true;
-                            obj.input.checked = true;
-                            obj.root.className = "popup-checkbox disabled";
-                        }
-                    }
-                    var included = specification.interfaces.options.find(function (element, index, array) {
-                        if (element.name == this.name) {
-                            return true;
-                        } else {
-                            return false;
-                        }
-                    }, obj);
-                    if (included != undefined) {
-                        obj.input.checked = true;
-                    }
-                    obj.handleEvent();
-                    this.options.push(obj);
-                    this.dynamicContent.appendChild(obj.root);
-                }
-            }
-            this.continue = function () {
-                popupObject.hide();
-                convert.convert(document.getElementById('content'));
-            }
-            this.back = function () {
-                popupObject.postNode(popupStateNodes.state[3]);
-            }
-        }
-        this.state[5] = new function () {
-            this.title = "Add/Edit Survey Element";
-            this.content = document.createElement("div");
-            this.content.id = "state-1";
-            var spnH = document.createElement('div');
-            var span = document.createElement("span");
-            span.textContent = "You can configure your survey element here. Press 'Continue' to complete your changes.";
-            spnH.appendChild(span);
-            this.content.appendChild(spnH);
-            this.dynamic = document.createElement("div");
-            this.option = null;
-            this.parent = null;
-            this.optionLists = [];
-            this.select = document.createElement("select");
-            this.select.setAttribute("name", "type");
-            this.select.addEventListener("change", this, false);
-            this.content.appendChild(this.select);
-            this.content.appendChild(this.dynamic);
-            this.generate = function (option, parent) {
-                this.option = option;
-                this.parent = parent;
-                if (this.select.childElementCount == 0) {
-                    var optionList = specification.schema.getAllElementsByName("survey")[0].getAllElementsByName("type")[0].getAllElementsByTagName("xs:enumeration");
-                    for (var i = 0; i < optionList.length; i++) {
-                        var selectOption = document.createElement("option");
-                        selectOption.value = optionList[i].getAttribute("value");
-                        selectOption.textContent = selectOption.value;
-                        this.select.appendChild(selectOption);
-                    }
-                }
-                if (this.option.type != undefined) {
-                    this.select.value = this.option.type
-                } else {
-                    this.select.value = "statement";
-                    this.option.type = "statement";
-                }
-
-                this.dynamic.innerHTML = null;
-                var statement = document.createElement("div");
-                var statementText = document.createElement("span");
-                var statementEntry = document.createElement("input");
-                statement.appendChild(statementText);
-                statement.appendChild(statementEntry);
-                statement.className = "survey-entry-attribute";
-                statementText.textContent = "Statement/Question";
-                statementEntry.style.width = "500px";
-                statementEntry.addEventListener("change", this, false);
-                statementEntry.setAttribute("name", "statement");
-                statementEntry.value = this.option.statement;
-                this.dynamic.appendChild(statement);
-
-                var id = document.createElement("div");
-                var idText = document.createElement("span");
-                var idEntry = document.createElement("input");
-                id.appendChild(idText);
-                id.appendChild(idEntry);
-                id.className = "survey-entry-attribute";
-                idText.textContent = "ID: ";
-                idEntry.addEventListener("change", this, false);
-                idEntry.setAttribute("name", "id");
-                idEntry.value = this.option.id;
-
-                this.dynamic.appendChild(id);
-
-                switch (this.option.type) {
-                    case "statement":
-                        break;
-                    case "question":
-                        var boxsizeSelect = document.createElement("select");
-                        var optionList = specification.schema.getAllElementsByName("survey")[0].getAllElementsByName("boxsize")[0].getAllElementsByTagName("xs:enumeration");
-                        for (var i = 0; i < optionList.length; i++) {
-                            var selectOption = document.createElement("option");
-                            selectOption.value = optionList[i].getAttribute("value");
-                            selectOption.textContent = selectOption.value;
-                            boxsizeSelect.appendChild(selectOption);
-                        }
-                        if (this.option.boxsize != undefined) {
-                            boxsizeSelect.value = this.option.boxsize;
-                        } else {
-                            boxsizeSelect.value = "normal";
-                            this.option.boxsize = "normal";
-                        }
-                        boxsizeSelect.setAttribute("name", "boxsize");
-                        boxsizeSelect.addEventListener("change", this, false);
-                        var boxsize = document.createElement("div");
-                        var boxsizeText = document.createElement("span");
-                        boxsizeText.textContent = "Entry Size: ";
-                        boxsize.appendChild(boxsizeText);
-                        boxsize.appendChild(boxsizeSelect);
-                        boxsize.className = "survey-entry-attribute";
-                        this.dynamic.appendChild(boxsize);
-
-                        var mandatory = document.createElement("div");
-                        var mandatoryInput = document.createElement("input");
-                        var mandatoryText = document.createElement("span");
-                        mandatoryText.textContent = "Mandatory: ";
-                        mandatory.appendChild(mandatoryText);
-                        mandatory.appendChild(mandatoryInput);
-                        mandatory.className = "survey-entry-attribute";
-                        mandatoryInput.type = "checkbox";
-                        if (this.option.mandatory) {
-                            mandatoryInput.checked = true;
-                        } else {
-                            mandatoryInput.checked = false;
-                        }
-                        mandatoryInput.setAttribute("name", "mandatory");
-                        mandatoryInput.addEventListener("change", this, false);
-                        this.dynamic.appendChild(mandatory);
-                        break;
-                    case "number":
-                        this.dynamic.appendChild(id);
-
-                        var mandatory = document.createElement("div");
-                        var mandatoryInput = document.createElement("input");
-                        var mandatoryText = document.createElement("span");
-                        mandatoryText.textContent = "Mandatory: ";
-                        mandatory.appendChild(mandatoryText);
-                        mandatory.appendChild(mandatoryInput);
-                        mandatory.className = "survey-entry-attribute";
-                        mandatoryInput.type = "checkbox";
-                        if (this.option.mandatory) {
-                            mandatoryInput.checked = true;
-                        } else {
-                            mandatoryInput.checked = false;
-                        }
-                        mandatoryInput.setAttribute("name", "mandatory");
-                        mandatoryInput.addEventListener("change", this, false);
-                        this.dynamic.appendChild(mandatory);
-
-                        var minimum = document.createElement("div");
-                        var minimumEntry = document.createElement("input");
-                        var minimumText = document.createElement("span");
-                        minimumText.textContent = "Minimum: ";
-                        minimum.appendChild(minimumText);
-                        minimum.appendChild(minimumEntry);
-                        minimum.className = "survey-entry-attribute";
-                        minimumEntry.type = "number";
-                        minimumEntry.setAttribute("name", "min");
-                        minimumEntry.addEventListener("change", this, false);
-                        minimumEntry.value = this.option.min;
-                        this.dynamic.appendChild(minimum);
-
-                        var maximum = document.createElement("div");
-                        var maximumEntry = document.createElement("input");
-                        var maximumText = document.createElement("span");
-                        maximumText.textContent = "Maximum: ";
-                        maximum.appendChild(maximumText);
-                        maximum.appendChild(maximumEntry);
-                        maximum.className = "survey-entry-attribute";
-                        maximumEntry.type = "number";
-                        maximumEntry.setAttribute("name", "max");
-                        maximumEntry.addEventListener("change", this, false);
-                        maximumEntry.value = this.option.max;
-                        this.dynamic.appendChild(maximum);
-                        break;
-                    case "checkbox":
-                    case "radio":
-                        this.dynamic.appendChild(id);
-                        var optionHolder = document.createElement("div");
-                        optionHolder.className = 'node';
-                        optionHolder.id = 'popup-option-holder';
-                        var optionObject = function (parent, option) {
-                            this.rootDOM = document.createElement("div");
-                            this.rootDOM.className = "popup-option-entry";
-                            this.inputName = document.createElement("input");
-                            this.inputName.setAttribute("name", "name");
-                            this.inputLabel = document.createElement("input");
-                            this.inputLabel.setAttribute("name", "text");
-                            this.specification = option;
-                            this.parent = parent;
-                            this.handleEvent = function () {
-                                var target = event.currentTarget.getAttribute("name");
-                                eval("this.specification." + target + " = event.currentTarget.value");
-                            };
-
-                            var nameText = document.createElement("span");
-                            nameText.textContent = "Name: ";
-                            var labelText = document.createElement("span");
-                            labelText.textContent = "Label: ";
-                            this.rootDOM.appendChild(nameText);
-                            this.rootDOM.appendChild(this.inputName);
-                            this.rootDOM.appendChild(labelText);
-                            this.rootDOM.appendChild(this.inputLabel);
-                            this.inputName.addEventListener("change", this, false);
-                            this.inputLabel.addEventListener("change", this, false);
-                            this.inputName.value = this.specification.name;
-                            this.inputLabel.value = this.specification.text;
-                            this.inputLabel.style.width = "350px";
-
-                            this.deleteEntry = {
-                                root: document.createElement("button"),
-                                parent: this,
-                                handleEvent: function () {
-                                    document.getElementById("popup-option-holder").removeChild(this.parent.rootDOM);
-                                    var index = this.parent.parent.option.options.findIndex(function (element, index, array) {
-                                        if (element == this.parent.specification)
-                                            return true;
-                                        else
-                                            return false;
-                                    }, this);
-                                    var optionList = this.parent.parent.option.options;
-                                    if (index == optionList.length - 1) {
-                                        optionList = optionList.slice(0, index);
-                                    } else {
-                                        optionList = optionList.slice(0, index).concat(optionList.slice(index + 1));
-                                    }
-                                    this.parent.parent.option.options = optionList;
-                                }
-                            };
-                            this.deleteEntry.root.textContent = "Delete Option";
-                            this.deleteEntry.root.addEventListener("click", this.deleteEntry, false);
-                            this.rootDOM.appendChild(this.deleteEntry.root);
-                        }
-                        this.addEntry = {
-                            parent: this,
-                            root: document.createElement("button"),
-                            handleEvent: function () {
-                                var node = {
-                                    name: "name",
-                                    text: "text"
-                                };
-                                var optionsList = this.parent.option.options;
-                                optionsList.push(node);
-                                var obj = new optionObject(this.parent, optionsList[optionsList.length - 1]);
-                                this.parent.optionLists.push(obj);
-                                document.getElementById("popup-option-holder").appendChild(obj.rootDOM);
-                            }
-                        }
-                        this.addEntry.root.textContent = "Add Option";
-                        this.addEntry.root.addEventListener("click", this.addEntry);
-                        this.dynamic.appendChild(this.addEntry.root);
-                        for (var i = 0; i < this.option.options.length; i++) {
-                            var obj = new optionObject(this, this.option.options[i]);
-                            this.optionLists.push(obj);
-                            optionHolder.appendChild(obj.rootDOM);
-                        }
-                        this.dynamic.appendChild(optionHolder);
-                }
-            }
-            this.handleEvent = function (event) {
-                var name = event.currentTarget.getAttribute("name");
-                var nodeName = event.currentTarget.nodeName;
-                if (name == "type" && nodeName == "SELECT") {
-                    // If type has changed, we may need to rebuild the entire state node
-                    if (event.currentTarget.value != this.option.name) {
-                        this.option.type = event.currentTarget.value;
-                        this.generate(this.option, this.parent);
-                    }
-                    return;
-                }
-                switch (event.currentTarget.getAttribute("type")) {
-                    case "checkbox":
-                        eval("this.option." + name + " = event.currentTarget.checked");
-                        break;
-                    default:
-                        eval("this.option." + name + " = event.currentTarget.value");
-                        break;
-                }
-            }
-            this.continue = function () {
-                if (this.parent.type == "surveyNode") {
-                    var newNode = new this.parent.surveyEntryNode(this.parent, this.option);
-                    this.parent.children.push(newNode);
-                    this.parent.childrenDOM.appendChild(newNode.rootDOM);
-                } else if (this.parent.type == "surveyEntryNode") {
-                    this.parent.build();
-                }
-                popupObject.hide();
-            }
-        }
-        this.state[6] = new function () {
-            this.title = "Edit Scale Markers";
-            this.content = document.createElement("div");
-            this.content.id = "state-6";
-            var spnH = document.createElement('div');
-            var span = document.createElement("span");
-            span.textContent = "You can edit your scale markers here for the selected interface.";
-            spnH.appendChild(span);
-            this.scaleRoot;
-            this.parent;
-            this.markerNodes = [];
-            this.preset = {
-                input: document.createElement("select"),
-                parent: this,
-                handleEvent: function (event) {
-                    this.parent.scaleRoot.scales = [];
-                    var protoScale = interfaceSpecs.getAllElementsByTagName('scaledefinitions')[0].getAllElementsByName(event.currentTarget.value)[0];
-                    var protoMarkers = protoScale.getElementsByTagName("scalelabel");
-                    for (var i = 0; i < protoMarkers.length; i++) {
-                        var marker = {
-                            position: protoMarkers[i].getAttribute("position"),
-                            text: protoMarkers[i].textContent
-                        }
-                        this.parent.scaleRoot.scales.push(marker);
-                    }
-                    this.parent.buildMarkerList();
-                }
-            }
-            this.preset.input.addEventListener("change", this.preset);
-            this.content.appendChild(this.preset.input);
-            var optionHolder = document.createElement("div");
-            optionHolder.className = 'node';
-            optionHolder.id = 'popup-option-holder';
-            this.content.appendChild(optionHolder);
-            this.addMarker = {
-                root: document.createElement("button"),
-                parent: this,
-                handleEvent: function () {
-                    var marker = {
-                        position: 0,
-                        text: "text"
-                    };
-                    this.parent.scaleRoot.scales.push(marker);
-                    var markerNode = new this.parent.buildMarkerNode(this.parent, marker);
-                    document.getElementById("popup-option-holder").appendChild(markerNode.root);
-                    this.parent.markerNodes.push(markerNode);
-                }
-            };
-            this.addMarker.root.textContent = "Add Marker";
-            this.addMarker.root.addEventListener("click", this.addMarker);
-            this.generate = function (scaleRoot, parent) {
-                this.scaleRoot = scaleRoot;
-                this.parent = parent;
-
-                // Generate Pre-Set dropdown
-                var protoScales = interfaceSpecs.getAllElementsByTagName('scaledefinitions')[0].getElementsByTagName("scale");
-                this.preset.input.innerHTML = "";
-
-                for (var i = 0; i < protoScales.length; i++) {
-                    var selectOption = document.createElement("option");
-                    var scaleName = protoScales[i].getAttribute("name");
-                    selectOption.setAttribute("name", scaleName);
-                    selectOption.textContent = scaleName;
-                    this.preset.input.appendChild(selectOption);
-                }
-                this.content.appendChild(this.addMarker.root);
-
-                // Create Marker List
-                this.buildMarkerList();
-            }
-            this.buildMarkerList = function () {
-                var markerInject = document.getElementById("popup-option-holder");
-                markerInject.innerHTML = "";
-                this.markerNodes = [];
-                for (var i = 0; i < this.scaleRoot.scales.length; i++) {
-                    var markerNode = new this.buildMarkerNode(this, this.scaleRoot.scales[i]);
-                    markerInject.appendChild(markerNode.root);
-                    this.markerNodes.push(markerNode);
-
-                }
-            }
-
-            this.buildMarkerNode = function (parent, specification) {
-                this.root = document.createElement("div");
-                this.root.className = "popup-option-entry";
-                this.positionInput = document.createElement("input");
-                this.positionInput.min = 0;
-                this.positionInput.max = 100;
-                this.positionInput.value = specification.position;
-                this.positionInput.setAttribute("name", "position");
-                this.textInput = document.createElement("input");
-                this.textInput.setAttribute("name", "text");
-                this.textInput.style.width = "300px";
-                this.textInput.value = specification.text;
-                this.specification = specification;
-                this.parent = parent;
-                this.handleEvent = function (event) {
-                    switch (event.currentTarget.getAttribute("name")) {
-                        case "position":
-                            this.specification.position = Number(event.currentTarget.value);
-                            break;
-                        case "text":
-                            this.specification.text = event.currentTarget.value;
-                            break;
-                    }
-                }
-                this.positionInput.addEventListener("change", this, false);
-                this.textInput.addEventListener("change", this, false);
-
-                var posText = document.createElement("span");
-                posText.textContent = "Position: ";
-                var textText = document.createElement("span");
-                textText.textContent = "Text: ";
-                this.root.appendChild(posText);
-                this.root.appendChild(this.positionInput);
-                this.root.appendChild(textText);
-                this.root.appendChild(this.textInput);
-
-                this.deleteMarker = {
-                    root: document.createElement("button"),
-                    parent: this,
-                    handleEvent: function () {
-                        var index = this.parent.parent.scaleRoot.scales.findIndex(function (element, index, array) {
-                            if (element == this) {
-                                return true;
-                            } else {
-                                return false;
-                            }
-                        }, this.parent.specification)
-                        if (index >= 0) {
-                            this.parent.parent.scaleRoot.scales.splice(index, 1);
-                        }
-                        document.getElementById("popup-option-holder").removeChild(this.parent.root);
-                    }
-                }
-                this.deleteMarker.root.addEventListener("click", this.deleteMarker);
-                this.deleteMarker.root.textContent = "Delete Marker"
-                this.root.appendChild(this.deleteMarker.root);
-            }
-        }
-    }
-}
-
-function SpecificationToHTML() {
-    // This takes the specification node and converts it to an on-page HTML object
-    // Each Specification Node is given its own JS object which listens to the XSD for instant verification
-    // Once generated, it directly binds into the specification object to update with changes
-    // Fixed DOM entries
-    this.injectDOM;
-    this.setupDOM;
-    this.pages = [];
-
-    // Self-contained generators
-    this.createGeneralNodeDOM = function (name, id, parent) {
-        this.type = name;
-        var root = document.createElement('div');
-        root.id = id;
-        root.className = "node";
-
-        var titleDiv = document.createElement('div');
-        titleDiv.className = "node-title";
-        var title = document.createElement('span');
-        title.className = "node-title";
-        title.textContent = name;
-        titleDiv.appendChild(title);
-
-        var attributeDiv = document.createElement('div');
-        attributeDiv.className = "node-attributes";
-
-        var childrenDiv = document.createElement('div');
-        childrenDiv.className = "node-children";
-
-        var buttonsDiv = document.createElement('div');
-        buttonsDiv.className = "node-buttons";
-
-        root.appendChild(titleDiv);
-        root.appendChild(attributeDiv);
-        root.appendChild(childrenDiv);
-        root.appendChild(buttonsDiv);
-
-        var obj = {
-            rootDOM: root,
-            titleDOM: title,
-            attributeDOM: attributeDiv,
-            attributes: [],
-            childrenDOM: childrenDiv,
-            children: [],
-            buttonDOM: buttonsDiv,
-            parent: parent
-        }
-        return obj;
-    }
-
-    this.convertAttributeToDOM = function (node, schema) {
-        // This takes an attribute schema node and returns an object with the input node and any bindings
-        if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined) {
-            schema = specification.schema.getAllElementsByName(schema.getAttribute('ref'))[0];
-        }
-        var obj = new function () {
-            this.input;
-            this.name;
-            this.owner;
-            this.holder;
-
-            this.name = schema.getAttribute('name');
-            this.default = schema.getAttribute('default');
-            this.dataType = schema.getAttribute('type');
-            if (this.dataType == undefined) {
-                if (schema.childElementCount > 0) {
-                    if (schema.firstElementChild.nodeName == "xs:simpleType") {
-                        this.dataType = schema.getAllElementsByTagName("xs:restriction")[0].getAttribute("base");
-                    }
-                }
-            }
-            if (typeof this.dataType == "string") {
-                this.dataType = this.dataType.substr(3);
-            } else {
-                this.dataType = "string";
-            }
-            var minVar = undefined;
-            var maxVar = undefined;
-            switch (this.dataType) {
-                case "negativeInteger":
-                    maxVar = -1;
-                    break;
-                case "positiveInteger":
-                    minVar = 1;
-                    break;
-                case "nonNegativeInteger":
-                    minVar = 0;
-                    break;
-                case "nonPositiveInteger":
-                    maxVar = 0;
-                    break;
-                case "byte":
-                    minVar = 0;
-                    maxVar = 256;
-                    break;
-                case "short":
-                    minVar = 0;
-                    maxVar = 65536;
-                    break;
-                default:
-                    break;
-            }
-
-            this.enumeration = schema.getAllElementsByTagName("xs:enumeration");
-            if (this.enumeration.length == 0) {
-                this.input = document.createElement('input');
-                switch (this.dataType) {
-                    case "boolean":
-                        this.input.type = "checkbox";
-                        break;
-                    case "negativeInteger":
-                    case "positiveInteger":
-                    case "nonNegativeInteger":
-                    case "nonPositiveInteger":
-                    case "integer":
-                    case "short":
-                    case "byte":
-                        this.input.step = 1;
-                    case "decimal":
-                        this.input.type = "number";
-                        this.input.min = minVar;
-                        this.input.max = maxVar;
-                        break;
-                    default:
-                        break;
-                }
-            } else {
-                this.input = document.createElement("select");
-                for (var i = 0; i < this.enumeration.length; i++) {
-                    var option = document.createElement("option");
-                    var value = this.enumeration[i].getAttribute("value");
-                    option.setAttribute("value", value);
-                    option.textContent = value;
-                    this.input.appendChild(option);
-                }
-            }
-            var value;
-            eval("value = node." + this.name)
-            if (this.default != undefined && value == undefined) {
-                value = this.default;
-            }
-            if (this.input.type == "checkbox") {
-                if (value == "true" || value == "True") {
-                    this.input.checked = false;
-                } else {
-                    this.input.checked = false;
-                }
-            } else {
-                this.input.value = value;
-            }
-            this.handleEvent = function (event) {
-                var value;
-                if (this.input.nodeName == "INPUT") {
-                    switch (this.input.type) {
-                        case "checkbox":
-                            value = event.currentTarget.checked;
-                            break;
-                        case "number":
-                            if (event.currentTarget.value != "") {
-                                value = Number(event.currentTarget.value);
-                            } else {
-                                value = undefined;
-                            }
-                            break;
-                        default:
-                            if (event.currentTarget.value != "") {
-                                value = event.currentTarget.value;
-                            } else {
-                                value = undefined;
-                            }
-                            break;
-                    }
-                } else if (this.input.nodeName == "SELECT") {
-                    value = event.currentTarget.value;
-                }
-                eval("this.owner." + this.name + " = value");
-            }
-            this.holder = document.createElement('div');
-            this.holder.className = "attribute";
-            this.holder.setAttribute('name', this.name);
-            var text = document.createElement('span');
-            eval("text.textContent = attributeText." + this.name + "+': '");
-            this.holder.appendChild(text);
-            this.holder.appendChild(this.input);
-            this.owner = node;
-            this.input.addEventListener("change", this, false);
-        }
-        if (obj.attribute != null) {
-            obj.input.value = obj.attribute;
-        }
-        return obj;
-    }
-
-    this.convert = function (root) {
-        //Performs the actual conversion using the given root DOM as the root
-        this.injectDOM = root;
-
-        // Build the export button
-        var exportButton = document.createElement("button");
-        exportButton.textContent = "Export to XML";
-        exportButton.onclick = function () {
-            var doc = specification.encode();
-            var obj = {};
-            obj.title = "Export";
-            obj.content = document.createElement("div");
-            obj.content.id = "finish";
-            var span = document.createElement("span");
-            span.textContent = "Your XML document is linked below. On most browsers, simply right click on the link and select 'Save As'. Or clicking on the link may download the file directly."
-            obj.content.appendChild(span);
-            span = document.createElement("p");
-            span.textContent = "NOTE FOR SAFARI! You cannot right click on the below link and save it as a file, Safari does not like that at all. Instead click on it to open the XML, the Press Cmd+S to open the save dialogue. Make sure you have 'save as Page Source' selected on the bottom of the window. Currently Safari has no plans to support the HTML 'download' attribute which causes this problem";
-            obj.content.appendChild(span);
-            var link = document.createElement("div");
-            link.appendChild(doc.firstChild);
-            var file = [link.innerHTML];
-            var bb = new Blob(file, {
-                type: 'application/xml'
-            });
-            var dnlk = window.URL.createObjectURL(bb);
-            var a = document.createElement("a");
-            a.hidden = '';
-            a.href = dnlk;
-            a.download = "project-specification.xml";
-            a.textContent = "Save File";
-            obj.content.appendChild(a);
-            popupObject.show();
-            popupObject.postNode(obj);
-        }
-        this.injectDOM.appendChild(exportButton);
-
-        // First perform the setupNode;
-        var setupSchema = specification.schema.getAllElementsByName('setup')[0];
-        this.setupDOM = new this.createGeneralNodeDOM('Global Configuration', 'setup', null);
-        this.injectDOM.appendChild(this.setupDOM.rootDOM);
-        var setupAttributes = setupSchema.getAllElementsByTagName('xs:attribute');
-        for (var i = 0; i < setupAttributes.length; i++) {
-            var attributeName = setupAttributes[i].getAttribute('name');
-            var attrObject = this.convertAttributeToDOM(specification, setupAttributes[i]);
-            this.setupDOM.attributeDOM.appendChild(attrObject.holder);
-            this.setupDOM.attributes.push(attrObject);
-        }
-
-        // Build the exit Text node
-        var exitText = new this.createGeneralNodeDOM("Exit Text", "exit-test", this.setupDOM);
-        exitText.rootDOM.removeChild(exitText.attributeDOM);
-        this.setupDOM.children.push(exitText);
-        this.setupDOM.childrenDOM.appendChild(exitText.rootDOM);
-        var obj = {
-            rootDOM: document.createElement("div"),
-            labelDOM: document.createElement("label"),
-            inputDOM: document.createElement("textarea"),
-            parent: exitText,
-            specification: specification,
-            handleEvent: function (event) {
-                this.specification.exitText = this.inputDOM.value;
-            }
-        }
-        var exitWarning = document.createElement("div");
-        obj.rootDOM.appendChild(exitWarning);
-        exitWarning.textContent = "Only visible when the above 'On complete redirect URL' field is empty.";
-        obj.rootDOM.appendChild(obj.labelDOM);
-        obj.rootDOM.appendChild(obj.inputDOM);
-        obj.labelDOM.textContent = "Text: ";
-        obj.inputDOM.value = obj.specification.exitText;
-        obj.inputDOM.addEventListener("change", obj);
-        exitText.children.push(obj);
-        exitText.childrenDOM.appendChild(obj.rootDOM);
-
-        // Now we must build the interface Node
-        this.interfaceDOM = new this.interfaceNode(this, specification.interfaces);
-        this.interfaceDOM.build("Interface", "setup-interface", this.setupDOM.rootDOM);
-
-        // Now build the Metrics selection node
-        var metric = this.createGeneralNodeDOM("Session Metrics", "setup-metric", this.setupDOM);
-        metric.rootDOM.removeChild(metric.attributeDOM);
-        this.setupDOM.children.push(metric);
-        this.setupDOM.childrenDOM.appendChild(metric.rootDOM);
-        var interfaceName = popupStateNodes.state[1].select.value;
-        var checkText = interfaceSpecs.getElementsByTagName("global")[0].getAllElementsByTagName("metrics")[0];
-        var testXML = interfaceSpecs.getElementsByTagName("tests")[0].getAllElementsByName(interfaceName)[0];
-        var interfaceXML = interfaceSpecs.getAllElementsByTagName("interfaces")[0].getAllElementsByName(testXML.getAttribute("interface"))[0].getAllElementsByTagName("metrics")[0];
-        testXML = testXML.getAllElementsByTagName("metrics");
-        var interfaceXMLChild = interfaceXML.firstElementChild;
-        while (interfaceXMLChild) {
-            var obj = {
-                input: document.createElement('input'),
-                root: document.createElement('div'),
-                text: document.createElement('span'),
-                specification: specification.metrics.enabled,
-                name: interfaceXMLChild.getAttribute("name"),
-                handleEvent: function () {
-                    for (var i = 0; i < this.specification.length; i++) {
-                        if (this.specification[i] == this.name) {
-                            var options = this.specification;
-                            if (this.input.checked == false) {
-                                if (i == options.length) {
-                                    options = options.slice(0, i);
-                                } else {
-                                    options = options.slice(0, i).concat(options.slice(i + 1));
-                                }
-                            } else {
-                                return;
-                            }
-                            this.specification = options;
-                            break;
-                        }
-                    }
-                    if (this.input.checked) {
-                        this.specification.push(this.name);
-                    }
-                }
-            };
-            obj.root.className = "attribute";
-            obj.input.type = "checkbox";
-            obj.root.appendChild(obj.text);
-            obj.root.appendChild(obj.input);
-            obj.text.textContent = checkText.getAllElementsByName(interfaceXMLChild.getAttribute("name"))[0].textContent;
-            metric.children.push(obj);
-            metric.childrenDOM.appendChild(obj.root);
-            for (var j = 0; j < specification.metrics.enabled.length; j++) {
-                if (specification.metrics.enabled[j] == obj.name) {
-                    obj.input.checked = true;
-                    break;
-                }
-            }
-            interfaceXMLChild = interfaceXMLChild.nextElementSibling;
-        }
-
-        // Now both before and after surveys
-        if (specification.preTest == undefined) {
-            specification.preTest = new specification.surveyNode(specification);
-            specification.preTest.location = "pre";
-        }
-        if (specification.postTest == undefined) {
-            specification.postTest = new specification.surveyNode(specification);
-            specification.postTest.location = "post";
-        }
-        var surveyBefore = new this.surveyNode(this, specification.preTest, "Pre");
-        var surveyAfter = new this.surveyNode(this, specification.postTest, "Post");
-        this.setupDOM.children.push(surveyBefore);
-        this.setupDOM.children.push(surveyAfter);
-        this.setupDOM.childrenDOM.appendChild(surveyBefore.rootDOM);
-        this.setupDOM.childrenDOM.appendChild(surveyAfter.rootDOM);
-
-        // Add in the page creator button
-        this.addPage = {
-            root: document.createElement("button"),
-            parent: this,
-            handleEvent: function () {
-                var pageObj = new specification.page(specification);
-                specification.pages.push(pageObj);
-                var newPage = new this.parent.pageNode(this.parent, pageObj);
-                document.getElementById("page-holder").appendChild(newPage.rootDOM);
-                this.parent.pages.push(newPage);
-            }
-        }
-        this.addPage.root.textContent = "Add Page";
-        this.addPage.root.id = "new-page-button";
-        this.addPage.root.style.float = "left";
-        this.addPage.root.addEventListener("click", this.addPage, false);
-
-        var pageHolder = document.createElement("div");
-        pageHolder.id = "page-holder";
-        this.injectDOM.appendChild(pageHolder);
-
-        // Build each page
-        for (var page of specification.pages) {
-            var newPage = new this.pageNode(this, page);
-            pageHolder.appendChild(newPage.rootDOM);
-            this.pages.push(newPage);
-        }
-
-        this.injectDOM.appendChild(this.addPage.root);
-    }
-
-    this.interfaceNode = function (parent, rootObject) {
-        this.type = "interfaceNode";
-        this.rootDOM;
-        this.titleDOM;
-        this.attributeDOM;
-        this.attributes = [];
-        this.childrenDOM;
-        this.children = [];
-        this.buttonDOM;
-        this.parent = parent;
-        this.HTMLPoint;
-        this.specification = rootObject;
-        this.schema = specification.schema.getAllElementsByName("interface")[1];
-
-        this.createIOasAttr = function (name, specification, parent, type) {
-            this.root = document.createElement('div');
-            this.input = document.createElement("input");
-            this.name = name;
-            this.type = type;
-            this.parent = parent;
-            this.specification = specification;
-            this.handleEvent = function (event) {
-                for (var i = 0; i < this.specification.options.length; i++) {
-                    if (this.specification.options[i].name == this.name) {
-                        var options = this.specification.options;
-                        if (this.input.checked == false) {
-                            if (i == options.length) {
-                                options = options.slice(0, i);
-                            } else {
-                                options = options.slice(0, i).concat(options.slice(i + 1));
-                            }
-                        } else {
-                            return;
-                        }
-                        this.specification.options = options;
-                        break;
-                    }
-                }
-                if (this.input.checked) {
-                    var obj = {
-                        name: this.name,
-                        type: this.type
-                    };
-                    this.specification.options.push(obj);
-                }
-                if (this.parent.HTMLPoint.id == "setup") {
-                    // We've changed a global setting, must update all child 'interfaces' and disable them
-                    for (pages of convert.pages) {
-                        for (interface of pages.interfaces) {
-                            if (this.type == "check") {
-                                for (node of interface.children[0].attributes) {
-                                    if (node.name == this.name) {
-                                        if (this.input.checked) {
-                                            node.input.disabled = true;
-                                            node.input.checked = false;
-                                        } else {
-                                            node.input.disabled = false;
-                                        }
-                                        break;
-                                    }
-                                }
-                            } else if (this.type == "show") {
-                                for (node of interface.children[1].attributes) {
-                                    if (node.name == this.name) {
-                                        if (this.input.checked) {
-                                            node.input.disabled = true;
-                                        } else {
-                                            node.input.disabled = false;
-                                        }
-                                        break;
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            };
-            this.findIndex = function (element, index, array) {
-                if (element.name == this.name)
-                    return true;
-                else
-                    return false;
-            };
-            this.findNode = function (element, index, array) {
-                if (element.name == this.name)
-                    return true;
-                else
-                    return false;
-            };
-            this.input.type = "checkbox";
-            this.input.setAttribute("name", name);
-            this.input.addEventListener("change", this, false);
-            this.root.appendChild(this.input);
-            this.root.className = "attribute";
-            return this;
-        }
-
-        this.build = function (name, id, parent) {
-            var obj = this.parent.createGeneralNodeDOM(name, id, parent);
-
-            this.rootDOM = obj.rootDOM;
-            this.titleDOM = obj.titleDOM;
-            this.attributeDOM = obj.attributeDOM;
-            this.childrenDOM = obj.childrenDOM;
-            this.buttonDOM = obj.buttonsDOM;
-            this.HTMLPoint = parent;
-            this.rootDOM.removeChild(this.attributeDOM);
-            if (parent.id != "setup") {
-                // Put in the <title> node:
-                this.titleNode = {
-                    root: document.createElement("div"),
-                    label: document.createElement("span"),
-                    input: document.createElement("input"),
-                    parent: this,
-                    handleEvent: function (event) {
-                        this.parent.specification.title = event.currentTarget.value;
-                    }
-                }
-                this.titleNode.label.textContent = "Presented Axis Title:";
-                this.titleNode.root.className = "node-children";
-                this.titleNode.root.appendChild(this.titleNode.label);
-                this.titleNode.root.appendChild(this.titleNode.input);
-                this.titleNode.input.addEventListener("change", this.titleNode, false);
-                this.titleNode.input.value = this.specification.title;
-                this.children.push(this.titleNode);
-                this.childrenDOM.appendChild(this.titleNode.root);
-                // Set the interface-name attribute
-                this.axisName = {
-                    root: document.createElement("div"),
-                    label: document.createElement("span"),
-                    input: document.createElement("input"),
-                    parent: this,
-                    handleEvent: function (event) {
-                        this.parent.specification.name = event.currentTarget.value;
-                    }
-                }
-                this.axisName.label.textContent = "Saved Axis Name (no spaces):";
-                this.axisName.root.className = "node-children";
-                this.axisName.root.appendChild(this.axisName.label);
-                this.axisName.root.appendChild(this.axisName.input);
-                this.axisName.input.addEventListener("change", this.axisName, false);
-                this.axisName.input.value = this.specification.name;
-                this.children.push(this.axisName);
-                this.childrenDOM.appendChild(this.axisName.root);
-            }
-
-            // Put in the check / show options as individual children
-            var checks = this.parent.createGeneralNodeDOM("Checks", "setup-interface-checks", this);
-
-            var interfaceName = popupStateNodes.state[1].select.value;
-            var checkText = interfaceSpecs.getElementsByTagName("global")[0].getAllElementsByTagName("checks")[0];
-            var testXML = interfaceSpecs.getElementsByTagName("tests")[0].getAllElementsByName(interfaceName)[0];
-            var interfaceXML = interfaceSpecs.getAllElementsByTagName("interfaces")[0].getAllElementsByName(testXML.getAttribute("interface"))[0].getAllElementsByTagName("checks")[0];
-            testXML = testXML.getAllElementsByTagName("checks");
-            var interfaceXMLChild = interfaceXML.firstElementChild;
-            while (interfaceXMLChild) {
-                var obj = new this.createIOasAttr(interfaceXMLChild.getAttribute("name"), this.specification, this, "check");
-                for (var option of this.specification.options) {
-                    if (option.name == obj.name) {
-                        obj.input.checked = true;
-                        break;
-                    }
-                }
-                if (parent.id != "setup") {
-                    var node = convert.interfaceDOM.children[0].attributes.find(obj.findNode, obj);
-                    if (node != undefined) {
-                        if (node.input.checked) {
-                            obj.input.checked = false;
-                            obj.input.disabled = true;
-                        }
-                    }
-                }
-                var text = document.createElement('span');
-                text.textContent = checkText.getAllElementsByName(interfaceXMLChild.getAttribute("name"))[0].textContent;
-                obj.root.appendChild(text);
-                checks.attributeDOM.appendChild(obj.root);
-                checks.attributes.push(obj);
-                interfaceXMLChild = interfaceXMLChild.nextElementSibling;
-            }
-            this.children.push(checks);
-            this.childrenDOM.appendChild(checks.rootDOM);
-
-            var show = this.parent.createGeneralNodeDOM("Show", "setup-interface-show", this);
-            interfaceName = popupStateNodes.state[1].select.value;
-            checkText = interfaceSpecs.getElementsByTagName("global")[0].getAllElementsByTagName("show")[0];
-            testXML = interfaceSpecs.getElementsByTagName("tests")[0].getAllElementsByName(interfaceName)[0];
-            interfaceXML = interfaceSpecs.getAllElementsByTagName("interfaces")[0].getAllElementsByName(testXML.getAttribute("interface"))[0].getAllElementsByTagName("show")[0];
-            testXML = testXML.getAllElementsByTagName("show");
-            interfaceXMLChild = interfaceXML.firstElementChild;
-            while (interfaceXMLChild) {
-                var obj = new this.createIOasAttr(interfaceXMLChild.getAttribute("name"), this.specification, this, "show");
-                for (var option of this.specification.options) {
-                    if (option.name == obj.name) {
-                        obj.input.checked = true;
-                        break;
-                    }
-                }
-                if (parent.id != "setup") {
-                    var node = convert.interfaceDOM.children[0].attributes.find(obj.findNode, obj);
-                    if (node != undefined) {
-                        if (node.input.checked) {
-                            obj.input.checked = false;
-                            obj.input.disabled = true;
-                        }
-                    }
-                }
-                var text = document.createElement('span');
-                text.textContent = checkText.getAllElementsByName(interfaceXMLChild.getAttribute("name"))[0].textContent;
-                obj.root.appendChild(text);
-                show.attributeDOM.appendChild(obj.root);
-                show.attributes.push(obj);
-                interfaceXMLChild = interfaceXMLChild.nextElementSibling;
-            }
-            this.children.push(show);
-            this.childrenDOM.appendChild(show.rootDOM);
-
-            if (parent.id == "setup") {} else {
-                var nameAttr = this.parent.convertAttributeToDOM(this, specification.schema.getAllElementsByName("name")[0]);
-                this.attributeDOM.appendChild(nameAttr.holder);
-                this.attributes.push(nameAttr);
-                var scales = new this.scalesNode(this, this.specification);
-                this.children.push(scales);
-                this.childrenDOM.appendChild(scales.rootDOM);
-            }
-            if (parent != undefined) {
-                parent.appendChild(this.rootDOM);
-            }
-        }
-
-        this.scalesNode = function (parent, rootObject) {
-            this.type = "scalesNode";
-            this.rootDOM = document.createElement("div");
-            this.titleDOM = document.createElement("span");
-            this.attributeDOM = document.createElement("div");
-            this.attributes = [];
-            this.childrenDOM = document.createElement("div");
-            this.children = [];
-            this.buttonDOM = document.createElement("div");
-            this.parent = parent;
-            this.specification = rootObject;
-            this.schema = specification.schema.getAllElementsByName("page")[0];
-            this.rootDOM.className = "node";
-
-            var titleDiv = document.createElement('div');
-            titleDiv.className = "node-title";
-            this.titleDOM.className = "node-title";
-            this.titleDOM.textContent = "Interface Scales";
-            titleDiv.appendChild(this.titleDOM);
-
-            this.attributeDOM.className = "node-attributes";
-            this.childrenDOM.className = "node-children";
-            this.buttonDOM.className = "node-buttons";
-
-            this.rootDOM.appendChild(titleDiv);
-            this.rootDOM.appendChild(this.attributeDOM);
-            this.rootDOM.appendChild(this.childrenDOM);
-            this.rootDOM.appendChild(this.buttonDOM);
-
-            this.editButton = {
-                button: document.createElement("button"),
-                parent: this,
-                handleEvent: function (event) {
-                    popupObject.show();
-                    popupObject.postNode(popupStateNodes.state[6]);
-                    popupStateNodes.state[6].generate(this.parent.specification, this.parent);
-                }
-            };
-            this.editButton.button.textContent = "Edit Scales/Markers";
-            this.editButton.button.addEventListener("click", this.editButton, false);
-            this.buttonDOM.appendChild(this.editButton.button);
-        }
-    }
-
-    this.surveyNode = function (parent, rootObject, location) {
-        this.type = "surveyNode";
-        this.rootDOM = document.createElement("div");
-        this.titleDOM = document.createElement("span");
-        this.attributeDOM = document.createElement("div");
-        this.attributes = [];
-        this.childrenDOM = document.createElement("div");
-        this.children = [];
-        this.buttonDOM = document.createElement("div");
-        this.parent = parent;
-        this.specification = rootObject;
-        this.schema = specification.schema.getAllElementsByName("survey")[1];
-        this.rootDOM.className = "node";
-
-        var titleDiv = document.createElement('div');
-        titleDiv.className = "node-title";
-        this.titleDOM.className = "node-title";
-        this.titleDOM.textContent = "Survey";
-        titleDiv.appendChild(this.titleDOM);
-
-        this.attributeDOM.className = "node-attributes";
-        var locationAttr = document.createElement("span");
-        this.attributeDOM.appendChild(locationAttr);
-        if (location == "Pre" || location == "pre") {
-            locationAttr.textContent = "Location: Before";
-        } else {
-            locationAttr.textContent = "Location: After";
-        }
-        this.childrenDOM.className = "node-children";
-        this.buttonDOM.className = "node-buttons";
-
-        this.rootDOM.appendChild(titleDiv);
-        this.rootDOM.appendChild(this.attributeDOM);
-        this.rootDOM.appendChild(this.childrenDOM);
-        this.rootDOM.appendChild(this.buttonDOM);
-
-        this.surveyEntryNode = function (parent, rootObject) {
-            this.type = "surveyEntryNode";
-            this.rootDOM = document.createElement("div");
-            this.titleDOM = document.createElement("span");
-            this.attributeDOM = document.createElement("div");
-            this.attributes = [];
-            this.childrenDOM = document.createElement("div");
-            this.children = [];
-            this.buttonDOM = document.createElement("div");
-            this.parent = parent;
-            this.specification = rootObject;
-            this.schema = specification.schema.getAllElementsByName("surveyentry")[1];
-
-            this.rootDOM.className = "node";
-            this.rootDOM.style.minWidth = "50%";
-
-            var titleDiv = document.createElement('div');
-            titleDiv.className = "node-title";
-            this.titleDOM.className = "node-title";
-            titleDiv.appendChild(this.titleDOM);
-
-            this.attributeDOM.className = "node-attributes";
-            this.childrenDOM.className = "node-children";
-            this.buttonDOM.className = "node-buttons";
-
-            this.rootDOM.appendChild(titleDiv);
-            this.rootDOM.appendChild(this.attributeDOM);
-            this.rootDOM.appendChild(this.childrenDOM);
-            this.rootDOM.appendChild(this.buttonDOM);
-
-            this.build = function () {
-                this.attributeDOM.innerHTML = null;
-                this.childrenDOM.innerHTML = null;
-                var statementRoot = document.createElement("div");
-                var statement = document.createElement("span");
-                statement.textContent = "Statement / Question: " + this.specification.statement;
-                statementRoot.appendChild(statement);
-                this.children.push(statementRoot);
-                this.childrenDOM.appendChild(statementRoot);
-                switch (this.specification.type) {
-                    case "statement":
-                        this.titleDOM.textContent = "Statement";
-                        break;
-                    case "question":
-                        this.titleDOM.textContent = "Question";
-                        var id = convert.convertAttributeToDOM(this.specification, specification.schema.getAllElementsByName("id")[0]);
-                        var mandatory = convert.convertAttributeToDOM(this.specification, specification.schema.getAllElementsByName("mandatory")[0]);
-                        var boxsize = convert.convertAttributeToDOM(this.specification, specification.schema.getAllElementsByName("boxsize")[0]);
-                        this.attributeDOM.appendChild(id.holder);
-                        this.attributes.push(id);
-                        this.attributeDOM.appendChild(mandatory.holder);
-                        this.attributes.push(mandatory);
-                        this.attributeDOM.appendChild(boxsize.holder);
-                        this.attributes.push(boxsize);
-                        break;
-                    case "number":
-                        this.titleDOM.textContent = "Number";
-                        var id = convert.convertAttributeToDOM(this.specification, specification.schema.getAllElementsByName("id")[0]);
-                        var mandatory = convert.convertAttributeToDOM(this.specification, specification.schema.getAllElementsByName("mandatory")[0]);
-                        var min = convert.convertAttributeToDOM(this.specification, specification.schema.getAllElementsByName("min")[0]);
-                        var max = convert.convertAttributeToDOM(this.specification, specification.schema.getAllElementsByName("max")[0]);
-                        this.attributeDOM.appendChild(id.holder);
-                        this.attributes.push(id);
-                        this.attributeDOM.appendChild(min.holder);
-                        this.attributes.push(min);
-                        this.attributeDOM.appendChild(max.holder);
-                        this.attributes.push(max);
-                        break;
-                    case "checkbox":
-                        this.titleDOM.textContent = "Checkbox";
-                        var id = convert.convertAttributeToDOM(this.specification, specification.schema.getAllElementsByName("id")[0]);
-                        this.attributeDOM.appendChild(id.holder);
-                        this.attributes.push(id);
-                        break;
-                    case "radio":
-                        this.titleDOM.textContent = "Radio";
-                        var id = convert.convertAttributeToDOM(this.specification, specification.schema.getAllElementsByName("id")[0]);
-                        this.attributeDOM.appendChild(id.holder);
-                        this.attributes.push(id);
-                        break;
-                }
-            }
-            this.build();
-
-            this.editNode = {
-                root: document.createElement("button"),
-                parent: this,
-                handleEvent: function () {
-                    popupObject.show();
-                    popupStateNodes.state[5].generate(this.parent.specification, this.parent);
-                    popupObject.postNode(popupStateNodes.state[5]);
-                }
-            }
-            this.editNode.root.textContent = "Edit Entry";
-            this.editNode.root.addEventListener("click", this.editNode, false);
-            this.buttonDOM.appendChild(this.editNode.root);
-
-            this.deleteNode = {
-                root: document.createElement("button"),
-                parent: this,
-                handleEvent: function () {
-                    var optionList = this.parent.parent.specification.options;
-                    var childList = this.parent.parent.children;
-                    for (var i = 0; i < this.parent.parent.specification.options.length; i++) {
-                        var option = this.parent.parent.specification.options[i];
-                        if (option == this.parent.specification) {
-                            this.parent.parent.childrenDOM.removeChild(this.parent.rootDOM);
-                            if (i == this.parent.parent.specification.options.length - 1) {
-                                optionList = optionList.slice(0, i);
-                                childList = childList.slice(0, i);
-                            } else {
-                                optionList = optionList.slice(0, i).concat(optionList.slice(i + 1));
-                                childList = childList.slice(0, i).concat(childList.slice(i + 1));
-                            }
-                            this.parent.parent.specification.options = optionList;
-                            this.parent.parent.children = childList;
-                        }
-                    }
-                }
-            }
-            this.deleteNode.root.textContent = "Delete Entry";
-            this.deleteNode.root.addEventListener("click", this.deleteNode, false);
-            this.buttonDOM.appendChild(this.deleteNode.root);
-
-            this.moveToPosition = function (new_index) {
-                new_index = Math.min(new_index, this.parent.children.length);
-                var curr_index = this.parent.children.findIndex(function (elem) {
-                    if (elem == this) {
-                        return true;
-                    } else {
-                        return false;
-                    }
-                }, this);
-                // Split at the current location to remove the node and shift all the children up
-                var tail = this.parent.children.splice(curr_index + 1);
-                this.parent.children.pop();
-                this.parent.children = this.parent.children.concat(tail);
-
-                //Split at the new location and insert the node
-                tail = this.parent.children.splice(new_index);
-                this.parent.children.push(this);
-                this.parent.children = this.parent.children.concat(tail);
-
-                // Re-build the specification
-                this.parent.specification.options = [];
-                this.parent.childrenDOM.innerHTML = "";
-                for (var obj of this.parent.children) {
-                    this.parent.specification.options.push(obj.specification);
-                    this.parent.childrenDOM.appendChild(obj.rootDOM);
-                }
-                this.parent.children.forEach(function (obj, index) {
-                    obj.moveButtons.disable(index);
-                });
-            }
-
-            this.moveButtons = {
-                root_up: document.createElement("button"),
-                root_down: document.createElement("button"),
-                parent: this,
-                handleEvent: function (event) {
-                    var index = this.parent.parent.children.indexOf(this.parent);
-                    if (event.currentTarget.getAttribute("direction") == "up") {
-                        index = Math.max(index - 1, 0);
-                    } else if (event.currentTarget.getAttribute("direction") == "down") {
-                        index = Math.min(index + 1, this.parent.parent.children.length - 1);
-                    }
-                    this.parent.moveToPosition(index);
-                    this.disable(index);
-                },
-                disable: function (index) {
-                    if (index == 0) {
-                        this.root_up.disabled = true;
-                    } else {
-                        this.root_up.disabled = false;
-                    }
-                    if (index == this.parent.parent.children.length - 1) {
-                        this.root_down.disabled = true;
-                    } else {
-                        this.root_down.disabled = false;
-                    }
-                }
-            }
-            this.moveButtons.root_up.setAttribute("direction", "up");
-            this.moveButtons.root_down.setAttribute("direction", "down");
-            this.moveButtons.root_up.addEventListener("click", this.moveButtons, false);
-            this.moveButtons.root_down.addEventListener("click", this.moveButtons, false);
-            this.moveButtons.root_up.textContent = "Move Up";
-            this.moveButtons.root_down.textContent = "Move Down";
-            this.buttonDOM.appendChild(this.moveButtons.root_up);
-            this.buttonDOM.appendChild(this.moveButtons.root_down);
-        }
-        this.addNode = {
-            root: document.createElement("button"),
-            parent: this,
-            handleEvent: function () {
-                var newNode = new this.parent.specification.OptionNode(this.parent.specification);
-                this.parent.specification.options.push(newNode);
-                popupObject.show();
-                popupStateNodes.state[5].generate(newNode, this.parent);
-                popupObject.postNode(popupStateNodes.state[5]);
-            }
-        }
-        this.addNode.root.textContent = "Add Survey Entry";
-        this.addNode.root.addEventListener("click", this.addNode, false);
-        this.buttonDOM.appendChild(this.addNode.root);
-
-        for (var option of this.specification.options) {
-            var newNode = new this.surveyEntryNode(this, option);
-            this.children.push(newNode);
-            this.childrenDOM.appendChild(newNode.rootDOM);
-        }
-
-        this.children.forEach(function (obj, index) {
-            obj.moveButtons.disable(index);
-        });
-    }
-
-    this.pageNode = function (parent, rootObject) {
-        this.type = "pageNode";
-        this.rootDOM = document.createElement("div");
-        this.titleDOM = document.createElement("span");
-        this.attributeDOM = document.createElement("div");
-        this.attributes = [];
-        this.childrenDOM = document.createElement("div");
-        this.children = [];
-        this.buttonDOM = document.createElement("div");
-        this.parent = parent;
-        this.specification = rootObject;
-        this.schema = specification.schema.getAllElementsByName("page")[0];
-        this.rootDOM.className = "node";
-
-        var titleDiv = document.createElement('div');
-        titleDiv.className = "node-title";
-        this.titleDOM.className = "node-title";
-        this.titleDOM.textContent = "Test Page";
-        titleDiv.appendChild(this.titleDOM);
-
-        this.attributeDOM.className = "node-attributes";
-        this.childrenDOM.className = "node-children";
-        this.buttonDOM.className = "node-buttons";
-
-        this.rootDOM.appendChild(titleDiv);
-        this.rootDOM.appendChild(this.attributeDOM);
-        this.rootDOM.appendChild(this.childrenDOM);
-        this.rootDOM.appendChild(this.buttonDOM);
-
-        // Do the comment prefix node
-        var cpn = this.parent.createGeneralNodeDOM("Comment Prefix", "" + this.specification.id + "-commentprefix", this.parent);
-        cpn.rootDOM.removeChild(cpn.attributeDOM);
-        var obj = {
-            root: document.createElement("div"),
-            input: document.createElement("input"),
-            parent: this,
-            handleEvent: function () {
-                this.parent.specification.commentBoxPrefix = event.currentTarget.value;
-            }
-        }
-        cpn.children.push(obj);
-        cpn.childrenDOM.appendChild(obj.root);
-        obj.root.appendChild(obj.input);
-        obj.input.addEventListener("change", obj, false);
-        obj.input.value = this.specification.commentBoxPrefix;
-        this.childrenDOM.appendChild(cpn.rootDOM);
-        this.children.push(cpn);
-
-        // Now both before and after surveys
-        if (this.specification.preTest == undefined) {
-            this.specification.preTest = new specification.surveyNode(specification);
-            this.specification.preTest.location = "pre";
-        }
-        if (this.specification.postTest == undefined) {
-            this.specification.postTest = new specification.surveyNode(specification);
-            this.specification.postTest.location = "post";
-        }
-        var surveyBefore = new this.parent.surveyNode(this, this.specification.preTest, "Pre");
-        var surveyAfter = new this.parent.surveyNode(this, this.specification.postTest, "Post");
-        this.children.push(surveyBefore);
-        this.children.push(surveyAfter);
-        this.childrenDOM.appendChild(surveyBefore.rootDOM);
-        this.childrenDOM.appendChild(surveyAfter.rootDOM);
-
-        // Build the attributes
-        var attributeList = this.schema.getAllElementsByTagName("xs:attribute");
-        for (var i = 0; i < attributeList.length; i++) {
-            var attributeName = attributeList[i].getAttribute('name');
-            var attrObject = this.parent.convertAttributeToDOM(rootObject, attributeList[i]);
-            this.attributeDOM.appendChild(attrObject.holder);
-            this.attributes.push(attrObject);
-        }
-
-        this.interfaces = [];
-
-        this.getAudioElements = function () {
-            var array = [];
-            for (var i = 0; i < this.children.length; i++) {
-                if (this.children[i].type == "audioElementNode") {
-                    array[array.length] = this.children[i];
-                }
-            }
-            return array;
-        }
-
-        this.redrawChildren = function () {
-            this.childrenDOM.innerHTML = "";
-            for (var child of this.children) {
-                this.childrenDOM.appendChild(child.rootDOM);
-            }
-        }
-
-        this.audioElementNode = function (parent, rootObject) {
-            this.type = "audioElementNode";
-            this.rootDOM = document.createElement("div");
-            this.titleDOM = document.createElement("span");
-            this.attributeDOM = document.createElement("div");
-            this.attributes = [];
-            this.childrenDOM = document.createElement("div");
-            this.children = [];
-            this.buttonDOM = document.createElement("div");
-            this.parent = parent;
-            this.specification = rootObject;
-            this.schema = specification.schema.getAllElementsByName("audioelement")[0];
-            this.rootDOM.className = "node";
-
-            var titleDiv = document.createElement('div');
-            titleDiv.className = "node-title";
-            this.titleDOM.className = "node-title";
-            this.titleDOM.textContent = "Audio Element";
-            titleDiv.appendChild(this.titleDOM);
-
-            this.attributeDOM.className = "node-attributes";
-            this.childrenDOM.className = "node-children";
-            this.buttonDOM.className = "node-buttons";
-
-            this.rootDOM.appendChild(titleDiv);
-            this.rootDOM.appendChild(this.attributeDOM);
-            this.rootDOM.appendChild(this.childrenDOM);
-            this.rootDOM.appendChild(this.buttonDOM);
-
-            // Build the attributes
-            var attributeList = this.schema.getAllElementsByTagName("xs:attribute");
-            for (var i = 0; i < attributeList.length; i++) {
-                var attributeName = attributeList[i].getAttribute('name');
-                var attrObject = this.parent.parent.convertAttributeToDOM(rootObject, attributeList[i]);
-                this.attributeDOM.appendChild(attrObject.holder);
-                this.attributes.push(attrObject);
-            }
-
-            this.deleteNode = {
-                root: document.createElement("button"),
-                parent: this,
-                handleEvent: function () {
-                    var i = this.parent.parent.specification.audioElements.findIndex(this.findNode, this);
-                    if (i >= 0) {
-                        var aeList = this.parent.parent.specification.audioElements;
-                        if (i < aeList.length - 1) {
-                            aeList = aeList.slice(0, i).concat(aeList.slice(i + 1));
-                        } else {
-                            aeList = aeList.slice(0, i);
-                        }
-                    }
-                    i = this.parent.parent.children.findIndex(function (element, index, array) {
-                        if (element == this.parent)
-                            return true;
-                        else
-                            return false;
-                    }, this);
-                    if (i >= 0) {
-                        var childList = this.parent.children;
-                        if (i < aeList.length - 1) {
-                            childList = childList.slice(0, i).concat(childList.slice(i + 1));
-                        } else {
-                            childList = childList.slice(0, i);
-                        }
-                        this.parent.parent.childrenDOM.removeChild(this.parent.rootDOM);
-                    }
-                },
-                findNode: function (element, index, array) {
-                    if (element == this.parent.specification)
-                        return true;
-                    else
-                        return false;
-                }
-            }
-            this.deleteNode.root.textContent = "Delete Entry";
-            this.deleteNode.root.addEventListener("click", this.deleteNode, false);
-            this.buttonDOM.appendChild(this.deleteNode.root);
-
-            this.moveButtons = {
-                root_up: document.createElement("button"),
-                root_down: document.createElement("button"),
-                parent: this,
-                handleEvent: function (event) {
-                    var index = this.parent.parent.getAudioElements().indexOf(this.parent);
-                    if (event.currentTarget.getAttribute("direction") == "up") {
-                        index = Math.max(index - 1, 0);
-                    } else if (event.currentTarget.getAttribute("direction") == "down") {
-                        index = Math.min(index + 1, this.parent.parent.getAudioElements().length - 1);
-                    }
-                    this.parent.moveToPosition(index);
-                    this.disable(index);
-                },
-                disable: function (index) {
-                    if (index == 0) {
-                        this.root_up.disabled = true;
-                    } else {
-                        this.root_up.disabled = false;
-                    }
-                    if (index == this.parent.parent.getAudioElements().length - 1) {
-                        this.root_down.disabled = true;
-                    } else {
-                        this.root_down.disabled = false;
-                    }
-                }
-            }
-            this.moveButtons.root_up.setAttribute("direction", "up");
-            this.moveButtons.root_down.setAttribute("direction", "down");
-            this.moveButtons.root_up.addEventListener("click", this.moveButtons, false);
-            this.moveButtons.root_down.addEventListener("click", this.moveButtons, false);
-            this.moveButtons.root_up.textContent = "Move Up";
-            this.moveButtons.root_down.textContent = "Move Down";
-            this.buttonDOM.appendChild(this.moveButtons.root_up);
-            this.buttonDOM.appendChild(this.moveButtons.root_down);
-
-            this.moveToPosition = function (new_index) {
-
-                // Get the zero-th Object
-                var zero_object = this.parent.getAudioElements()[0];
-                var parent_children_root_index = this.parent.children.indexOf(zero_object);
-                // splice out the array for processing
-                var process_array = this.parent.children.splice(parent_children_root_index);
-
-
-                new_index = Math.min(new_index, process_array.length);
-                var curr_index = process_array.findIndex(function (elem) {
-                    if (elem == this) {
-                        return true;
-                    } else {
-                        return false;
-                    }
-                }, this);
-
-                // Split at the current location to remove the node and shift all the children up
-                var tail = process_array.splice(curr_index + 1);
-                process_array.pop();
-                process_array = process_array.concat(tail);
-
-                //Split at the new location and insert the node
-                tail = process_array.splice(new_index);
-                process_array.push(this);
-                process_array = process_array.concat(tail);
-
-                // Re-attach to the parent.children
-                this.parent.children = this.parent.children.concat(process_array);
-
-                // Re-build the specification
-                this.parent.specification.audioElements = [];
-                for (var obj of process_array) {
-                    this.parent.specification.audioElements.push(obj.specification);
-                }
-                this.parent.redrawChildren();
-
-                process_array.forEach(function (obj, index) {
-                    obj.moveButtons.disable(index);
-                });
-
-            }
-        }
-
-        this.commentQuestionNode = function (parent, rootObject) {
-            this.type = "commentQuestionNode";
-            this.rootDOM = document.createElement("div");
-            this.titleDOM = document.createElement("span");
-            this.attributeDOM = document.createElement("div");
-            this.attributes = [];
-            this.childrenDOM = document.createElement("div");
-            this.children = [];
-            this.buttonDOM = document.createElement("div");
-            this.parent = parent;
-            this.specification = rootObject;
-            this.schema = specification.schema.getAllElementsByName("page")[0];
-            this.rootDOM.className = "node audio-element";
-
-            var titleDiv = document.createElement('div');
-            titleDiv.className = "node-title";
-            this.titleDOM.className = "node-title";
-            this.titleDOM.textContent = "Test Page";
-            titleDiv.appendChild(this.titleDOM);
-
-            this.attributeDOM.className = "node-attributes";
-            this.childrenDOM.className = "node-children";
-            this.buttonDOM.className = "node-buttons";
-
-            this.rootDOM.appendChild(titleDiv);
-            this.rootDOM.appendChild(this.attributeDOM);
-            this.rootDOM.appendChild(this.childrenDOM);
-            this.rootDOM.appendChild(this.buttonDOM);
-
-        }
-
-        // Build the components
-        if (this.specification.interfaces.length == 0) {
-            this.specification.interfaces.push(new specification.interfaceNode(specification));
-        }
-        for (var interfaceObj of this.specification.interfaces) {
-            var newInterface = new this.parent.interfaceNode(this.parent, interfaceObj);
-            newInterface.build("Interface", "" + this.specification.id + "-interface", this.childrenDOM);
-            this.children.push(newInterface);
-            this.interfaces.push(newInterface);
-        }
-
-        for (var elements of this.specification.audioElements) {
-            var audioElementDOM = new this.audioElementNode(this, elements);
-            this.children.push(audioElementDOM);
-            this.childrenDOM.appendChild(audioElementDOM.rootDOM);
-        }
-
-        this.getAudioElements().forEach(function (elem) {
-            elem.moveButtons.disable();
-        });
-
-        this.addInterface = {
-            root: document.createElement("button"),
-            parent: this,
-            handleEvent: function () {
-                var InterfaceObj = new specification.interfaceNode(specification);
-                var newInterface = new this.parent.parent.interfaceNode(this.parent.parent, InterfaceObj);
-                newInterface.build("Interface", "" + this.parent.specification.id + "-interface", this.parent.childrenDOM);
-                this.parent.children.push(newInterface);
-                this.parent.specification.interfaces.push(InterfaceObj);
-                this.parent.interfaces.push(newInterface);
-            }
-        }
-        this.addInterface.root.textContent = "Add Interface";
-        this.addInterface.root.addEventListener("click", this.addInterface, false);
-        this.buttonDOM.appendChild(this.addInterface.root);
-
-        this.addAudioElement = {
-            root: document.createElement("button"),
-            parent: this,
-            handleEvent: function () {
-                var audioElementObject = new this.parent.specification.audioElementNode(specification);
-                var audioElementDOM = new this.parent.audioElementNode(this.parent, audioElementObject);
-                this.parent.specification.audioElements.push(audioElementObject);
-                this.parent.children.push(audioElementDOM);
-                this.parent.childrenDOM.appendChild(audioElementDOM.rootDOM);
-            }
-        }
-        this.addAudioElement.root.textContent = "Add Audio Element";
-        this.addAudioElement.root.addEventListener("click", this.addAudioElement, false);
-        this.buttonDOM.appendChild(this.addAudioElement.root);
-    }
-}
+        $s.page.audioElements.splice(index, 1);
+    };
+}]);
--- a/tests/examples/mushra_example.xml	Tue Jun 27 21:22:15 2017 +0100
+++ b/tests/examples/mushra_example.xml	Wed Jul 05 12:22:29 2017 +0100
@@ -48,6 +48,7 @@
             <interface>
                 <interfaceoption type="check" name="fragmentMoved" />
                 <interfaceoption type="check" name="scalerange" min="25" max="75" />
+                <interfaceoption type="show" name="fragmentSort" />
                 <interfaceoption type="show" name='playhead' />
                 <interfaceoption type="show" name="page-count" />
                 <interfaceoption type="show" name="volume" />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/examples/ordinal_example.xml	Wed Jul 05 12:22:29 2017 +0100
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+    <waet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="test-schema.xsd">
+        <setup interface="ordinal" projectReturn="save.php" randomiseOrder="true">
+            <metric>
+                <metricenable>testTimer</metricenable>
+                <metricenable>elementTimer</metricenable>
+                <metricenable>elementInitialPosition</metricenable>
+                <metricenable>elementTracker</metricenable>
+                <metricenable>elementFlagListenedTo</metricenable>
+                <metricenable>elementFlagMoved</metricenable>
+                <metricenable>elementListenTracker</metricenable>
+            </metric>
+            <interface>
+                <interfaceoption type="check" name="fragmentComment" />
+                <interfaceoption type="show" name='playhead' />
+                <interfaceoption type="show" name="page-count" />
+                <interfaceoption type="show" name="volume" />
+                <interfaceoption type="show" name="comments" />
+            </interface>
+        </setup>
+        <page id='test-0' hostURL="media/example/" randomiseOrder='true' repeatCount='0' loop='true' loudness="-23">
+            <title>Ordinal Evaluation</title>
+            <interface></interface>
+            <audioelement url="0.wav" id="track-1" />
+            <audioelement url="1.wav" id="track-2" />
+            <audioelement url="2.wav" id="track-3" />
+            <audioelement url="3.wav" id="track-4" />
+            <audioelement url="4.wav" id="track-5" />
+            <audioelement url="5.wav" id="track-6" />
+            <audioelement url="6.wav" id="track-7" />
+            <audioelement url="7.wav" id="track-8" />
+        </page>
+    </waet>
--- a/xml/test-schema.xsd	Tue Jun 27 21:22:15 2017 +0100
+++ b/xml/test-schema.xsd	Wed Jul 05 12:22:29 2017 +0100
@@ -419,6 +419,7 @@
                 <xs:sequence>
                     <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
                 </xs:sequence>
+                <xs:attribute ref="minWait" />
                 <xs:attribute ref="id" use="required" />
             </xs:complexType>
         </xs:element>