changeset 2618:fff464febd56

Merge branch 'master' into vnext
author Nicholas Jillings <nicholas.jillings@mail.bcu.ac.uk>
date Thu, 17 Nov 2016 13:08:00 +0000
parents 756d12f5c330 (diff) c821dc2e26f5 (current diff)
children 74e5d7a978ee
files js/core.js
diffstat 16 files changed, 754 insertions(+), 220 deletions(-) [+]
line wrap: on
line diff
--- a/css/core.css	Thu Nov 17 13:07:20 2016 +0000
+++ b/css/core.css	Thu Nov 17 13:08:00 2016 +0000
@@ -101,6 +101,12 @@
 table.popup-option-list tr td {
     padding: 5px;
 }
+div.survey-slider-text-holder {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    padding: 0px 15px;
+}
 button#popup-proceed {
     bottom: 10px;
     right: 10px;
@@ -245,3 +251,14 @@
     padding: 0 5px;
     height: 290px;
 }
+
+/*  Comment Boxes */
+
+div.comment-slider-text-holder {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+}
+div.comment-slider-text-holder span {
+    margin: 0px 5px;
+}
--- a/interfaces/AB.js	Thu Nov 17 13:07:20 2016 +0000
+++ b/interfaces/AB.js	Thu Nov 17 13:08:00 2016 +0000
@@ -316,7 +316,7 @@
             if (this.parent.specification.parent.playOne || specification.playOne) {
                 $('.comparator-button').text('Wait');
                 $('.comparator-button').attr("disabled", "true");
-                $(this.playback).removeAttr("disabled");
+                $(this.playback).css("disabled", "false");
             } else {
                 $('.comparator-button').text('Listen');
             }
@@ -351,6 +351,11 @@
     this.comparators = [];
     this.selected = null;
 
+    var labelType = audioHolderObject.label;
+    if (labelType == "default") {
+        labelType = "capital";
+    }
+
     // First generate the Audio Objects for the Audio Engine
     for (var index = 0; index < audioHolderObject.audioElements.length; index++) {
         var element = audioHolderObject.audioElements[index];
@@ -359,21 +364,7 @@
             var orNode = new interfaceContext.outsideReferenceDOM(audioObject, index, document.getElementById("outside-reference-holder"));
             audioObject.bindInterface(orNode);
         } else {
-            var label;
-            switch (audioObject.specification.parent.label) {
-                case "none":
-                    label = "";
-                    break;
-                case "number":
-                    label = "" + index;
-                    break;
-                case "letter":
-                    label = String.fromCharCode(97 + index);
-                    break;
-                default:
-                    label = String.fromCharCode(65 + index);
-                    break;
-            }
+            var label = interfaceContext.getLabel(labelType, index, audioHolderObject.labelStart);
             var node = new this.comparatorBox(audioObject, index, label);
             audioObject.bindInterface(node);
             this.comparators.push(node);
--- a/interfaces/ABX.js	Thu Nov 17 13:07:20 2016 +0000
+++ b/interfaces/ABX.js	Thu Nov 17 13:08:00 2016 +0000
@@ -364,19 +364,10 @@
         }
         var audioObject = audioEngineContext.newTrack(element);
         var label;
-        switch (audioObject.specification.parent.label) {
-            case "none":
-                label = "";
-                break;
-            case "number":
-                label = "" + index;
-                break;
-            case "letter":
-                label = String.fromCharCode(97 + index);
-                break;
-            default:
-                label = String.fromCharCode(65 + index);
-                break;
+        if (index == 0) {
+            label = "A";
+        } else {
+            label = "B";
         }
         var node = new this.interfaceObject(audioObject, label);
         audioObject.bindInterface(node);
--- a/interfaces/ape.js	Thu Nov 17 13:07:20 2016 +0000
+++ b/interfaces/ape.js	Thu Nov 17 13:08:00 2016 +0000
@@ -273,6 +273,10 @@
     feedbackHolder.innerHTML = "";
     sliderHolder.innerHTML = "";
 
+    // Set labelType if default to number
+    if (audioHolderObject.label == "default" || audioHolderObject.label == "") {
+        audioHolderObject.label = "number";
+    }
     // Set the page title
     if (typeof audioHolderObject.title == "string" && audioHolderObject.title.length > 0) {
         document.getElementById("test-title").textContent = audioHolderObject.title
@@ -592,22 +596,8 @@
     // Create a new slider object;
     this.parent = audioObject;
     this.trackSliderObjects = [];
-    this.label = null;
+    this.label = interfaceContext.getLabel(audioObject.specification.parent.label, index, audioObject.specification.parent.labelStart);
     this.playing = false;
-    switch (audioObject.specification.parent.label) {
-        case "letter":
-            this.label = String.fromCharCode(97 + index);
-            break;
-        case "capital":
-            this.label = String.fromCharCode(65 + index);
-            break;
-        case "none":
-            this.label = "";
-            break;
-        default:
-            this.label = "" + (index + 1);
-            break;
-    }
     for (var i = 0; i < interfaceContext.interfaceSliders.length; i++) {
         var trackObj = interfaceContext.interfaceSliders[i].createSliderObject(audioObject, this.label);
         this.trackSliderObjects.push(trackObj);
--- a/interfaces/discrete.js	Thu Nov 17 13:07:20 2016 +0000
+++ b/interfaces/discrete.js	Thu Nov 17 13:08:00 2016 +0000
@@ -150,7 +150,11 @@
     // Find all the audioElements from the audioHolder
     var index = 0;
     var interfaceScales = testState.currentStateMap.interfaces[0].scales;
-    $(page.audioElements).each(function (index, element) {
+    var labelType = page.label;
+    if (labelType == "default") {
+        labelType = "number";
+    }
+    $(page.audioElements).each(function (pageIndex, element) {
         // Find URL of track
         // In this jQuery loop, variable 'this' holds the current audioElement.
 
@@ -161,20 +165,7 @@
             audioObject.bindInterface(orNode);
         } else {
             // Create a slider per track
-            switch (audioObject.specification.parent.label) {
-                case "none":
-                    label = "";
-                    break;
-                case "letter":
-                    label = String.fromCharCode(97 + index);
-                    break;
-                case "capital":
-                    label = String.fromCharCode(65 + index);
-                    break;
-                default:
-                    label = "" + index;
-                    break;
-            }
+            var label = interfaceContext.getLabel(labelType, index, page.labelStart);
             var sliderObj = new discreteObject(audioObject, label, interfaceScales);
             sliderBox.appendChild(sliderObj.holder);
             audioObject.bindInterface(sliderObj);
--- a/interfaces/horizontal-sliders.js	Thu Nov 17 13:07:20 2016 +0000
+++ b/interfaces/horizontal-sliders.js	Thu Nov 17 13:08:00 2016 +0000
@@ -154,7 +154,11 @@
 
     // Find all the audioElements from the audioHolder
     var index = 0;
-    $(page.audioElements).each(function (index, element) {
+    var labelType = page.label;
+    if (labelType == "default") {
+        labelType = "number";
+    }
+    $(page.audioElements).each(function (pageIndex, element) {
         // Find URL of track
         // In this jQuery loop, variable 'this' holds the current audioElement.
 
@@ -165,20 +169,7 @@
             audioObject.bindInterface(orNode);
         } else {
             // Create a slider per track
-            switch (audioObject.specification.parent.label) {
-                case "none":
-                    label = "";
-                    break;
-                case "letter":
-                    label = String.fromCharCode(97 + index);
-                    break;
-                case "capital":
-                    label = String.fromCharCode(65 + index);
-                    break;
-                default:
-                    label = "" + index;
-                    break;
-            }
+            var label = interfaceContext.getLabel(labelType, index, page.labelStart);
             var sliderObj = new sliderObject(audioObject, label);
 
             if (typeof page.initialPosition === "number") {
--- a/interfaces/mushra.js	Thu Nov 17 13:07:20 2016 +0000
+++ b/interfaces/mushra.js	Thu Nov 17 13:08:00 2016 +0000
@@ -155,7 +155,12 @@
 
     // Find all the audioElements from the audioHolder
     var index = 0;
-    $(audioHolderObject.audioElements).each(function (index, element) {
+    var interfaceScales = testState.currentStateMap.interfaces[0].scales;
+    var labelType = page.label;
+    if (labelType == "default") {
+        labelType = "number";
+    }
+    $(audioHolderObject.audioElements).each(function (pageIndex, element) {
         // Find URL of track
         // In this jQuery loop, variable 'this' holds the current audioElement.
 
@@ -166,20 +171,7 @@
             audioObject.bindInterface(orNode);
         } else {
             // Create a slider per track
-            switch (audioObject.specification.parent.label) {
-                case "none":
-                    label = "";
-                    break;
-                case "letter":
-                    label = String.fromCharCode(97 + index);
-                    break;
-                case "capital":
-                    label = String.fromCharCode(65 + index);
-                    break;
-                default:
-                    label = "" + index;
-                    break;
-            }
+            var label = interfaceContext.getLabel(labelType, index, page.labelStart);
             var sliderObj = new sliderObject(audioObject, label);
 
             if (typeof audioHolderObject.initialPosition === "number") {
--- a/interfaces/timeline.js	Thu Nov 17 13:07:20 2016 +0000
+++ b/interfaces/timeline.js	Thu Nov 17 13:08:00 2016 +0000
@@ -106,27 +106,19 @@
     if (interfaceObj.commentBoxPrefix != undefined) {
         commentBoxPrefix = interfaceObj.commentBoxPrefix;
     }
-
-    $(page.audioElements).each(function (index, element) {
+    var index = 0;
+    var interfaceScales = testState.currentStateMap.interfaces[0].scales;
+    var labelType = page.label;
+    if (labelType == "default") {
+        labelType = "number";
+    }
+    $(page.audioElements).each(function (pageIndex, element) {
         var audioObject = audioEngineContext.newTrack(element);
         if (page.audioElements.type == 'outside-reference') {
             var refNode = interfaceContext.outsideReferenceDOM(audioObject, index, outsideReferenceHolder);
             audioObject.bindInterface(orNode);
         } else {
-            switch (audioObject.specification.parent.label) {
-                case "none":
-                    label = "";
-                    break;
-                case "letter":
-                    label = String.fromCharCode(97 + index);
-                    break;
-                case "capital":
-                    label = String.fromCharCode(65 + index);
-                    break;
-                default:
-                    label = "" + index;
-                    break;
-            }
+            var label = interfaceContext.getLabel(labelType, index, page.labelStart);
             var node = new interfaceObject(audioObject, label);
 
             content.appendChild(node.DOM);
@@ -506,7 +498,7 @@
                     break;
             }
             if (checkState == false) {
-                canContinue == false;
+                canContinue = false;
             }
         }
         if (!canContinue) {
--- a/js/core.js	Thu Nov 17 13:07:20 2016 +0000
+++ b/js/core.js	Thu Nov 17 13:08:00 2016 +0000
@@ -799,13 +799,13 @@
         } else if (node.specification.type == 'number') {
             var input = document.createElement('input');
             input.type = 'textarea';
-            if (node.min != null) {
+            if (node.specification.min != null) {
                 input.min = node.specification.min;
             }
-            if (node.max != null) {
+            if (node.specification.max != null) {
                 input.max = node.specification.max;
             }
-            if (node.step != null) {
+            if (node.specification.step != null) {
                 input.step = node.specification.step;
             }
             if (node.response != undefined) {
@@ -823,6 +823,30 @@
             iframe.className = "youtube";
             iframe.src = node.specification.url;
             this.popupResponse.appendChild(iframe);
+        } else if (node.specification.type == "slider") {
+            var hold = document.createElement('div');
+            var input = document.createElement('input');
+            input.type = 'range';
+            input.style.width = "90%";
+            if (node.specification.min != null) {
+                input.min = node.specification.min;
+            }
+            if (node.specification.max != null) {
+                input.max = node.specification.max;
+            }
+            if (node.response != undefined) {
+                input.value = node.response;
+            }
+            hold.className = "survey-slider-text-holder";
+            var minText = document.createElement('span');
+            var maxText = document.createElement('span');
+            minText.textContent = node.specification.leftText;
+            maxText.textContent = node.specification.rightText;
+            hold.appendChild(minText);
+            hold.appendChild(maxText);
+            this.popupResponse.appendChild(input);
+            this.popupResponse.appendChild(hold);
+            this.popupResponse.style.textAlign = "center";
         }
         if (this.currentIndex + 1 == this.popupOptions.length) {
             if (this.node.location == "pre") {
@@ -922,6 +946,33 @@
             console.log("Checkbox: " + node.specification.statement);
             var inputs = this.popupResponse.getElementsByTagName('input');
             node.response = [];
+            var numChecked = 0;
+            for (var i = 0; i < node.specification.options.length; i++) {
+                if (inputs[i].checked) {
+                    numChecked++;
+                }
+            }
+            if (node.specification.min != undefined) {
+                if (node.specification.max == undefined) {
+                    if (numChecked < node.specification.min) {
+                        var msg = "You must select at least " + node.specification.min + " option";
+                        if (node.specification.min > 1) {
+                            msg += "s";
+                        }
+                        interfaceContext.lightbox.post("Error", msg);
+                        return;
+                    }
+                } else {
+                    if (numChecked < node.specification.min || numChecked > node.specification.max) {
+                        if (node.specification.min == node.specification.max) {
+                            interfaceContext.lightbox.post("Error", "You must only select " + node.specification.min);
+                        } else {
+                            interfaceContext.lightbox.post("Error", "You must select between " + node.specification.min + " and " + node.specification.max);
+                        }
+                        return;
+                    }
+                }
+            }
             for (var i = 0; i < node.specification.options.length; i++) {
                 node.response.push({
                     name: node.specification.options[i].name,
@@ -1080,6 +1131,49 @@
                     break;
                 }
             }
+        } else if (node.specification.type == 'slider') {
+            var input = this.popupContent.getElementsByTagName('input')[0];
+            node.response = input.value;
+            for (var condition of node.specification.conditions) {
+                var pass = false;
+                switch (condition.check) {
+                    case "contains":
+                        console.log("Survey Element of type 'number' cannot interpret contains conditions. IGNORING");
+                        break;
+                    case "greaterThan":
+                        if (node.response > Number(condition.value)) {
+                            pass = true;
+                        }
+                        break;
+                    case "lessThan":
+                        if (node.response < Number(condition.value)) {
+                            pass = true;
+                        }
+                        break;
+                    case "equals":
+                        if (node.response == condition.value) {
+                            pass = true;
+                        }
+                        break;
+                }
+                var jumpID;
+                if (pass) {
+                    jumpID = condition.jumpToOnPass;
+                } else {
+                    jumpID = condition.jumpToOnFail;
+                }
+                if (jumpID != undefined) {
+                    var index = this.popupOptions.findIndex(function (item, index, element) {
+                        if (item.specification.id == jumpID) {
+                            return true;
+                        } else {
+                            return false;
+                        }
+                    }, this);
+                    this.currentIndex = index - 1;
+                    break;
+                }
+            }
         }
         this.currentIndex++;
         if (this.currentIndex < this.popupOptions.length) {
@@ -1694,21 +1788,20 @@
     };
 
     this.setSynchronousLoop = function () {
-        // Pads the signals so they are all exactly the same length
-        // Get the length of the longest signal.
-        var length = 0;
+        // Pads the signals so they are all exactly the same duration
+        // Get the duration of the longest signal.
+        var duration = 0;
         var maxId;
         for (var i = 0; i < this.audioObjects.length; i++) {
-            if (length < this.audioObjects[i].buffer.buffer.length) {
-                length = this.audioObjects[i].buffer.buffer.length;
+            if (duration < this.audioObjects[i].buffer.buffer.duration) {
+                duration = this.audioObjects[i].buffer.buffer.duration;
                 maxId = i;
             }
         }
         // Extract the audio and zero-pad
         for (var ao of this.audioObjects) {
-            var lengthDiff = length - ao.buffer.buffer.length;
-            if (lengthDiff > 0) {
-                ao.buffer.buffer = ao.buffer.copyBuffer(0, samplesToSeconds(lengthDiff, ao.buffer.buffer.sampleRate));
+            if (ao.buffer.buffer.duration !== duration) {
+                ao.buffer.buffer = ao.buffer.copyBuffer(0, duration - ao.buffer.buffer.duration);
             }
         }
     };
@@ -2603,6 +2696,63 @@
         this.resize();
     };
 
+    this.sliderBox = function (commentQuestion) {
+        this.specification = commentQuestion;
+        this.holder = document.createElement("div");
+        this.holder.className = 'comment-div';
+        this.string = document.createElement("span");
+        this.string.innerHTML = commentQuestion.statement;
+        this.slider = document.createElement("input");
+        this.slider.type = "range";
+        this.slider.min = commentQuestion.min;
+        this.slider.max = commentQuestion.max;
+        this.slider.step = commentQuestion.step;
+        this.slider.value = commentQuestion.value;
+        var br = document.createElement('br');
+
+        var textHolder = document.createElement("div");
+        textHolder.className = "comment-slider-text-holder";
+
+        this.leftText = document.createElement("span");
+        this.leftText.textContent = commentQuestion.leftText;
+        this.rightText = document.createElement("span");
+        this.rightText.textContent = commentQuestion.rightText;
+        textHolder.appendChild(this.leftText);
+        textHolder.appendChild(this.rightText);
+
+        this.holder.appendChild(this.string);
+        this.holder.appendChild(br);
+        this.holder.appendChild(this.slider);
+        this.holder.appendChild(textHolder);
+
+        this.exportXMLDOM = function (storePoint) {
+            var root = storePoint.parent.document.createElement('comment');
+            root.id = this.specification.id;
+            root.setAttribute('type', this.specification.type);
+            console.log("Question: " + this.string.textContent);
+            console.log("Response: " + this.slider.value);
+            var question = storePoint.parent.document.createElement('question');
+            question.textContent = this.string.textContent;
+            var response = storePoint.parent.document.createElement('response');
+            response.textContent = this.slider.value;
+            root.appendChild(question);
+            root.appendChild(response);
+            storePoint.XMLDOM.appendChild(root);
+            return root;
+        };
+        this.resize = function () {
+            var boxwidth = (window.innerWidth - 100) / 2;
+            if (boxwidth >= 600) {
+                boxwidth = 600;
+            } else if (boxwidth < 400) {
+                boxwidth = 400;
+            }
+            this.holder.style.width = boxwidth + "px";
+            this.slider.style.width = boxwidth - 24 + "px";
+        };
+        this.resize();
+    };
+
     this.createCommentQuestion = function (element) {
         var node;
         if (element.type == 'question') {
@@ -2611,6 +2761,8 @@
             node = new this.radioBox(element);
         } else if (element.type == 'checkbox') {
             node = new this.checkboxBox(element);
+        } else if (element.type == 'slider') {
+            node = new this.sliderBox(element);
         }
         this.commentQuestions.push(node);
         return node;
@@ -2886,8 +3038,8 @@
                 obj.input.value = obj.gain.gain.value;
                 obj.input.setAttribute('orient', 'vertical');
                 obj.input.type = "range";
-                obj.input.min = -6;
-                obj.input.max = 6;
+                obj.input.min = -12;
+                obj.input.max = 0;
                 obj.input.step = 0.25;
                 if (f0 != 1000) {
                     obj.input.value = (Math.random() * 12) - 6;
@@ -3102,6 +3254,71 @@
         node.textContent = errorMessage;
         testState.currentStore.XMLDOM.appendChild(node);
     };
+
+    this.getLabel = function (labelType, index, labelStart) {
+        /*
+            Get the correct label based on type, index and offset
+        */
+
+        function calculateLabel(labelType, index, offset) {
+            if (labelType == "none") {
+                return "";
+            }
+            switch (labelType) {
+                case "letter":
+                    return String.fromCharCode((index + offset) % 26 + 97);
+                case "capital":
+                    return String.fromCharCode((index + offset) % 26 + 65);
+                case "number":
+                    return String(index + offset);
+                default:
+                    return "";
+            }
+        }
+
+        if (typeof labelStart !== "string" || labelStart.length == 0) {
+            labelStart = String.fromCharCode(0);
+        }
+
+        switch (labelType) {
+            case "letter":
+                labelStart = labelStart.charCodeAt(0);
+                if (labelStart < 97 || labelStart > 122) {
+                    labelStart = 97;
+                }
+                labelStart -= 97;
+                break;
+            case "capital":
+                labelStart = labelStart.charCodeAt(0);
+                if (labelStart < 65 || labelStart > 90) {
+                    labelStart = 65;
+                }
+                labelStart -= 65;
+                break;
+            case "number":
+                labelStart = Number(labelStart);
+                if (!isFinite(labelStart)) {
+                    labelStart = 1;
+                }
+                break;
+            case "none":
+            default:
+                labelStart = 0;
+        }
+        if (typeof index == "number") {
+            return calculateLabel(labelType, index, labelStart);
+        } else if (index.length && index.length > 0) {
+            var a = [],
+                l = index.length,
+                i;
+            for (i = 0; i < l; i++) {
+                a[i] = calculateLabel(labelType, index[i], labelStart);
+            }
+            return a;
+        } else {
+            throw ("Invalid arguments");
+        }
+    }
 }
 
 function Storage() {
@@ -3257,14 +3474,17 @@
             switch (node.specification.type) {
                 case "number":
                 case "question":
+                case "slider":
                     var child = this.parent.document.createElement('response');
                     child.textContent = node.response;
                     surveyresult.appendChild(child);
                     break;
                 case "radio":
                     var child = this.parent.document.createElement('response');
-                    child.setAttribute('name', node.response.name);
-                    child.textContent = node.response.text;
+                    if (node.response !== null) {
+                        child.setAttribute('name', node.response.name);
+                        child.textContent = node.response.text;
+                    }
                     surveyresult.appendChild(child);
                     break;
                 case "checkbox":
--- a/js/specification.js	Thu Nov 17 13:07:20 2016 +0000
+++ b/js/specification.js	Thu Nov 17 13:08:00 2016 +0000
@@ -195,7 +195,7 @@
 
         this.OptionNode = function (specification) {
             this.type = undefined;
-            this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
+            this.schema = undefined;
             this.id = undefined;
             this.name = undefined;
             this.mandatory = undefined;
@@ -208,6 +208,7 @@
             this.conditions = [];
 
             this.decode = function (parent, child) {
+                this.schema = specification.schema.getAllElementsByName(child.nodeName)[0];
                 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
                 for (var i in attributeMap) {
                     if (isNaN(Number(i)) == true) {
@@ -226,6 +227,15 @@
                             break;
                     }
                 }
+                if (child.nodeName == 'surveyentry') {
+                    console.log("NOTE - Use of <surveyelement> is now deprecated. Whilst these will still work, newer nodes and tighter error checking will not be enforced");
+                    console.log("Please use the newer, type specifc nodes");
+                    if (!this.type) {
+                        throw ("Type not specified");
+                    }
+                } else {
+                    this.type = child.nodeName.split('survey')[1];
+                }
                 this.statement = child.getElementsByTagName('statement')[0].textContent;
                 if (this.type == "checkbox" || this.type == "radio") {
                     var children = child.getElementsByTagName('option');
@@ -242,6 +252,17 @@
                             });
                         }
                     }
+                } else if (this.type == "slider") {
+                    this.leftText = "";
+                    this.rightText = "";
+                    var minText = child.getElementsByTagName("minText");
+                    var maxText = child.getElementsByTagName("maxText");
+                    if (minText.length > 0) {
+                        this.leftText = minText[0].textContent;
+                    }
+                    if (maxText.length > 0) {
+                        this.rightText = maxText[0].textContent;
+                    }
                 }
                 var conditionElements = child.getElementsByTagName("conditional");
                 for (var i = 0; i < conditionElements.length; i++) {
@@ -257,8 +278,7 @@
             };
 
             this.exportXML = function (doc) {
-                var node = doc.createElement('surveyentry');
-                node.setAttribute('type', this.type);
+                var node = doc.createElement('survey' + this.type);
                 var statement = doc.createElement('statement');
                 statement.textContent = this.statement;
                 node.appendChild(statement);
@@ -275,6 +295,16 @@
                 }
                 switch (this.type) {
                     case "checkbox":
+                        if (this.min != undefined) {
+                            node.setAttribute("min", this.min);
+                        } else {
+                            node.setAttribute("min", "0");
+                        }
+                        if (this.max != undefined) {
+                            node.setAttribute("max", this.max);
+                        } else {
+                            node.setAttribute("max", "undefined");
+                        }
                     case "radio":
                         for (var i = 0; i < this.options.length; i++) {
                             var option = this.options[i];
@@ -283,6 +313,7 @@
                             optionNode.textContent = option.text;
                             node.appendChild(optionNode);
                         }
+                        break;
                     case "number":
                         if (this.min != undefined) {
                             node.setAttribute("min", this.min);
@@ -290,6 +321,7 @@
                         if (this.max != undefined) {
                             node.setAttribute("max", this.max);
                         }
+                        break;
                     case "question":
                         if (this.boxsize != undefined) {
                             node.setAttribute("boxsize", this.boxsize);
@@ -297,6 +329,29 @@
                         if (this.mandatory != undefined) {
                             node.setAttribute("mandatory", this.mandatory);
                         }
+                        break;
+                    case "video":
+                        if (this.mandatory != undefined) {
+                            node.setAttribute("mandatory", this.mandatory);
+                        }
+                    case "youtube":
+                        if (this.url != undefined) {
+                            node.setAttribute("url", this.url);
+                        }
+                        break;
+                    case "slider":
+                        node.setAttribute("min", this.min);
+                        node.setAttribute("max", this.max);
+                        if (this.leftText) {
+                            var minText = doc.createElement("minText");
+                            minText.textContent = this.leftText;
+                            node.appendChild(minText);
+                        }
+                        if (this.rightText) {
+                            var maxText = doc.createElement("maxText");
+                            maxText.textContent = this.rightText;
+                            node.appendChild(maxText);
+                        }
                     default:
                         break;
                 }
@@ -319,11 +374,12 @@
             } else if (this.location == 'after') {
                 this.location = 'post';
             }
-            var children = xml.getAllElementsByTagName('surveyentry');
-            for (var i = 0; i < children.length; i++) {
+            var child = xml.firstElementChild
+            while (child) {
                 var node = new this.OptionNode(this.specification);
-                node.decode(parent, children[i]);
+                node.decode(parent, child);
                 this.options.push(node);
+                child = child.nextElementSibling;
             }
             if (this.options.length == 0) {
                 console.log("Empty survey node");
@@ -459,6 +515,7 @@
         this.outsideReference = null;
         this.loudness = null;
         this.label = null;
+        this.labelStart = "";
         this.preTest = null;
         this.postTest = null;
         this.interfaces = [];
@@ -539,11 +596,15 @@
             }
 
             // Now decode the commentquestions
-            var commentQuestions = xml.getElementsByTagName('commentquestion');
-            for (var i = 0; i < commentQuestions.length; i++) {
-                var node = new this.commentQuestionNode(this.specification);
-                node.decode(parent, commentQuestions[i]);
-                this.commentQuestions.push(node);
+            var cqNode = xml.getElementsByTagName('commentquestions');
+            if (cqNode.length != 0) {
+                cqNode = cqNode[0];
+                var commentQuestions = cqNode.children;
+                for (var i = 0; i < commentQuestions.length; i++) {
+                    var node = new this.commentQuestionNode(this.specification);
+                    node.decode(parent, commentQuestions[i]);
+                    this.commentQuestions.push(node);
+                }
             }
         };
 
@@ -589,26 +650,85 @@
             this.id = null;
             this.name = undefined;
             this.type = undefined;
-            this.options = [];
             this.statement = undefined;
             this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
             this.decode = function (parent, xml) {
                 this.id = xml.id;
                 this.name = xml.getAttribute('name');
-                this.type = xml.getAttribute('type');
+                switch (xml.nodeName) {
+                    case "commentradio":
+                        this.type = "radio";
+                        this.options = [];
+                        break;
+                    case "commentcheckbox":
+                        this.type = "checkbox";
+                        this.options = [];
+                        break;
+                    case "commentslider":
+                        this.type = "slider";
+                        this.min = undefined;
+                        this.max = undefined;
+                        this.step = undefined;
+                        break;
+                    case "commentquestion":
+                    default:
+                        this.type = "question";
+                        break;
+                }
                 this.statement = xml.getElementsByTagName('statement')[0].textContent;
-                var optNodes = xml.getElementsByTagName('option');
-                for (var i = 0; i < optNodes.length; i++) {
-                    var optNode = optNodes[i];
-                    this.options.push({
-                        name: optNode.getAttribute('name'),
-                        text: optNode.textContent
-                    });
+                if (this.type == "radio" || this.type == "checkbox") {
+                    var optNodes = xml.getElementsByTagName('option');
+                    for (var i = 0; i < optNodes.length; i++) {
+                        var optNode = optNodes[i];
+                        this.options.push({
+                            name: optNode.getAttribute('name'),
+                            text: optNode.textContent
+                        });
+                    }
+                }
+                if (this.type == "slider") {
+                    this.min = Number(xml.getAttribute("min"));
+                    this.max = Number(xml.getAttribute("max"));
+                    this.step = Number(xml.getAttribute("step"));
+                    if (this.step == undefined) {
+                        this.step = 1;
+                    }
+                    this.value = Number(xml.getAttribute("value"));
+                    if (this.value == undefined) {
+                        this.value = min;
+                    }
+                    this.leftText = xml.getElementsByTagName("minText");
+                    if (this.leftText && this.leftText.length > 0) {
+                        this.leftText = this.leftText[0].textContent;
+                    } else {
+                        this.leftText = "";
+                    }
+                    this.rightText = xml.getElementsByTagName("maxText");
+                    if (this.rightText && this.rightText.length > 0) {
+                        this.rightText = this.rightText[0].textContent;
+                    } else {
+                        this.rightText = "";
+                    }
                 }
             };
 
             this.encode = function (root) {
-                var node = root.createElement("commentquestion");
+                var node;
+                switch (this.type) {
+                    case "radio":
+                        node = root.createElement("commentradio");
+                        break;
+                    case "checkbox":
+                        node = root.createElement("commentcheckbox");
+                        break;
+                    case "slider":
+                        node = root.createElement("commentslider");
+                        break;
+                    case "question":
+                    default:
+                        node = root.createElement("commentquestion");
+                        break;
+                }
                 node.id = this.id;
                 node.setAttribute("type", this.type);
                 if (this.name != undefined) {
@@ -617,11 +737,33 @@
                 var statement = root.createElement("statement");
                 statement.textContent = this.statement;
                 node.appendChild(statement);
-                for (var option of this.options) {
-                    var child = root.createElement("option");
-                    child.setAttribute("name", option.name);
-                    child.textContent = option.text;
-                    node.appendChild(child);
+                if (this.type == "radio" || this.type == "checkbox") {
+                    for (var option of this.options) {
+                        var child = root.createElement("option");
+                        child.setAttribute("name", option.name);
+                        child.textContent = option.text;
+                        node.appendChild(child);
+                    }
+                }
+                if (this.type == "slider") {
+                    node.setAttribute("min", this.min);
+                    node.setAttribute("max", this.max);
+                    if (this.step !== 1) {
+                        node.setAttribute("step", this.step);
+                    }
+                    if (this.value !== this.min) {
+                        node.setAttribute("value", this.value);
+                    }
+                    if (this.leftText.length > 0) {
+                        var leftText = root.createElement("minText");
+                        leftText.textContent = this.leftText;
+                        node.appendChild(leftText);
+                    }
+                    if (this.rightText.length > 0) {
+                        var rightText = root.createElement("maxText");
+                        rightText.textContent = this.rightText;
+                        node.appendChild(rightText);
+                    }
                 }
                 return node;
             };
--- a/python/generate_report.py	Thu Nov 17 13:07:20 2016 +0000
+++ b/python/generate_report.py	Thu Nov 17 13:08:00 2016 +0000
@@ -120,9 +120,10 @@
 
 # generate images for later use
 if render_figures:
-    subprocess.call("python timeline_view_movement.py '"+folder_name+"'", shell=True)
-    subprocess.call("python score_parser.py '"+folder_name+"'", shell=True)
-    subprocess.call("python score_plot.py '"+folder_name+"ratings/'", shell=True)
+    script_path = os.path.dirname(os.path.realpath(__file__)) # where is generate_report.py?
+    subprocess.call("python " +script_path+"/timeline_view_movement.py '"+folder_name+"'", shell=True)
+    subprocess.call("python " +script_path+"/score_parser.py '"+folder_name+"'", shell=True)
+    subprocess.call("python " +script_path+"/score_plot.py '"+folder_name+"ratings/'", shell=True)
 
 # make array of text and array of dates
 body_array = []
--- a/python/timeline_view_movement.py	Thu Nov 17 13:07:20 2016 +0000
+++ b/python/timeline_view_movement.py	Thu Nov 17 13:08:00 2016 +0000
@@ -124,9 +124,10 @@
                     stop_times_global  = []
                     listen_events = audioelement.findall("./metric/metricresult/[@name='elementListenTracker']/event")
                     for event in listen_events:
-                        # get testtime: start and stop
-                        start_times_global.append(float(event.find('testtime').get('start')))#-time_offset)
-                        stop_times_global.append(float(event.find('testtime').get('stop')))#-time_offset)
+                        if event.find('testtime') is not None:
+                            # get testtime: start and stop
+                            start_times_global.append(float(event.find('testtime').get('start')))#-time_offset)
+                            stop_times_global.append(float(event.find('testtime').get('stop')))#-time_offset)
                     
                     # display fragment name at start
                     plt.text(0,initial_position+0.02,audio_id,color=colormap[increment%len(colormap)]) #,rotation=45
@@ -289,12 +290,35 @@
                 plt.ylabel('Rating') # default
                 plt.ylim(0, 1) # rating between 0 and 1
 
-                # TO DO: 
-                # Y axis title and tick labels as specified in 'setup' for corresponding page
+                # Y axis title and tick labels as specified in 'setup'
+                # for corresponding page
+                page_setup = root.find("./waet/page[@id='"+page_name+"']") 
+                    # 'ref' of page is 'id' in page setup
+
                 # Different plots for different axes
+                interfaces = page_setup.findall("./interface")
+                interface_title = interfaces[0].find("./title")
+                scales = interfaces[0].findall("./scales") # get first interface by default
+                scalelabels = scales[0].findall("./scalelabel") # get first scale by default
+
+                labelpos = [] # array of scalelabel positions
+                labelstr = [] # array of strings at labels
+                for scalelabel in scalelabels:
+                    labelpos.append(float(scalelabel.get('position'))/100.0)
+                    labelstr.append(scalelabel.text)
+
+                # use interface name as Y axis label
+                if interface_title is not None:
+                    plt.ylabel(interface_title.text)
+                else:
+                    plt.ylabel('Rating') # default
+
+                if len(labelpos):
+                    plt.yticks(labelpos, labelstr)
             
                 #plt.show() # uncomment to show plot; comment when just saving
                 #exit()
             
+                # save as PDF
                 plt.savefig(timeline_folder+subject_id+"-"+page_name+".pdf", bbox_inches='tight')
                 plt.close()
--- a/test_create/attributes.json	Thu Nov 17 13:07:20 2016 +0000
+++ b/test_create/attributes.json	Thu Nov 17 13:08:00 2016 +0000
@@ -27,5 +27,9 @@
     "postSilence": "Post Silence",
     "poolSize": "Pool Size",
     "alwaysInclude": "Always Include",
-    "crossFade": "Cross Fade"
+    "crossFade": "Cross Fade",
+    "check": "Check",
+    "value": "Value",
+    "jumpToOnPass": "Jump To ID On Pass",
+    "jumpToOnFail": "Jump To ID On Fail"
 }
--- a/tests/examples/APE_example.xml	Thu Nov 17 13:07:20 2016 +0000
+++ b/tests/examples/APE_example.xml	Thu Nov 17 13:08:00 2016 +0000
@@ -6,7 +6,7 @@
                     <statement>Please enter your name.</statement>
                     <conditional check="equals" value="John" jumpToOnPass="test-intro" jumpToOnFail="checkboxtest" />
                 </surveyentry>
-                <surveyentry type="checkbox" id="checkboxtest" mandatory="true">
+                <surveyentry type="checkbox" id="checkboxtest" mandatory="true" min="2" max="4">
                     <statement>Please select with which activities you have any experience (example checkbox question)</statement>
                     <option name="musician">Playing a musical instrument</option>
                     <option name="soundengineer">Recording or mixing audio</option>
@@ -111,24 +111,26 @@
             <audioelement url="4.wav" gain="0.0" id="track-9" />
             <audioelement url="5.wav" gain="0.0" id="track-10" />
             <audioelement url="6.wav" gain="0.0" id="track-11" type="outside-reference" />
-            <commentquestion id='mixingExperience' type="question">
-                <statement>What is your general experience with numbers?</statement>
-            </commentquestion>
-            <commentquestion id="preference" type="radio">
-                <statement>Please enter your overall preference</statement>
-                <option name="worst">Very Bad</option>
-                <option name="bad"></option>
-                <option name="OK">OK</option>
-                <option name="Good"></option>
-                <option name="Great">Great</option>
-            </commentquestion>
-            <commentquestion id="character" type="checkbox">
-                <statement>Please describe the overall character</statement>
-                <option name="funky">Funky</option>
-                <option name="mellow">Mellow</option>
-                <option name="laidback">Laid back</option>
-                <option name="heavy">Heavy</option>
-            </commentquestion>
+            <commentquestions>
+                <commentquestion id='mixingExperience'>
+                    <statement>What is your general experience with numbers?</statement>
+                </commentquestion>
+                <commentradio id="preference">
+                    <statement>Please enter your overall preference</statement>
+                    <option name="worst">Very Bad</option>
+                    <option name="bad"></option>
+                    <option name="OK">OK</option>
+                    <option name="Good"></option>
+                    <option name="Great">Great</option>
+                </commentradio>
+                <commentcheckbox id="character" type="checkbox">
+                    <statement>Please describe the overall character</statement>
+                    <option name="funky">Funky</option>
+                    <option name="mellow">Mellow</option>
+                    <option name="laidback">Laid back</option>
+                    <option name="heavy">Heavy</option>
+                </commentcheckbox>
+            </commentquestions>
             <survey location="before">
                 <surveyentry type="statement" id="test-1-intro">
                     <statement>Example of an 'APE' style interface with hidden anchor 'zero' (which needs to be below 20%), looping of the samples, randomisation of marker labels, mandatory moving of every sample, and a forced scale usage of at least 25%-75%.</statement>
--- a/tests/examples/mushra_example.xml	Thu Nov 17 13:07:20 2016 +0000
+++ b/tests/examples/mushra_example.xml	Thu Nov 17 13:08:00 2016 +0000
@@ -101,24 +101,26 @@
             <audioelement url="4.wav" gain="0.0" id="track-9" />
             <audioelement url="5.wav" gain="0.0" id="track-10" />
             <audioelement url="1.wav" gain="0.0" id="track-11" type="outside-reference" />
-            <commentquestion id='mixingExperience' type="question">
-                <statement>What is your general experience with numbers?</statement>
-            </commentquestion>
-            <commentquestion id="preference" type="radio">
-                <statement>Please enter your overall preference</statement>
-                <option name="worst">Very Bad</option>
-                <option name="bad"></option>
-                <option name="OK">OK</option>
-                <option name="Good"></option>
-                <option name="Great">Great</option>
-            </commentquestion>
-            <commentquestion id="character" type="checkbox">
-                <statement>Please describe the overall character</statement>
-                <option name="funky">Funky</option>
-                <option name="mellow">Mellow</option>
-                <option name="laidback">Laid back</option>
-                <option name="heavy">Heavy</option>
-            </commentquestion>
+            <commentquestions>
+                <commentquestion id='mixingExperience' type="question">
+                    <statement>What is your general experience with numbers?</statement>
+                </commentquestion>
+                <commentquestion id="preference" type="radio">
+                    <statement>Please enter your overall preference</statement>
+                    <option name="worst">Very Bad</option>
+                    <option name="bad"></option>
+                    <option name="OK">OK</option>
+                    <option name="Good"></option>
+                    <option name="Great">Great</option>
+                </commentquestion>
+                <commentquestion id="character" type="checkbox">
+                    <statement>Please describe the overall character</statement>
+                    <option name="funky">Funky</option>
+                    <option name="mellow">Mellow</option>
+                    <option name="laidback">Laid back</option>
+                    <option name="heavy">Heavy</option>
+                </commentquestion>
+            </commentquestions>
             <survey location="before">
                 <surveyentry type="statement" id="test-1-intro">
                     <statement>Example of a 'MUSHRA' style interface with hidden anchor 'zero' (which needs to be below 20%), looping of the samples, randomisation of marker labels, mandatory moving of every sample, and a forced scale usage of at least 25%-75%.</statement>
--- a/xml/test-schema.xsd	Thu Nov 17 13:07:20 2016 +0000
+++ b/xml/test-schema.xsd	Thu Nov 17 13:08:00 2016 +0000
@@ -75,7 +75,7 @@
                     <xs:element name="commentboxprefix" type="xs:string" minOccurs="0" maxOccurs="1" />
                     <xs:element ref="interface" minOccurs="1" maxOccurs="unbounded" />
                     <xs:element ref="audioelement" minOccurs="1" maxOccurs="unbounded" />
-                    <xs:element ref="commentquestion" minOccurs="0" maxOccurs="unbounded" />
+                    <xs:element ref="commentquestions" minOccurs="0" maxOccurs="1" />
                     <xs:element ref="survey" minOccurs="0" maxOccurs="2" />
                 </xs:sequence>
                 <xs:attribute ref="id" use="required" />
@@ -96,6 +96,7 @@
                         </xs:restriction>
                     </xs:simpleType>
                 </xs:attribute>
+                <xs:attribute name="labelStart" type="xs:string" use="optional" default="" />
                 <xs:attribute ref="poolSize" />
                 <xs:attribute ref="alwaysInclude" />
                 <xs:attribute ref="preSilence" />
@@ -205,10 +206,21 @@
             </xs:complexType>
         </xs:element>
 
-        <xs:element name="commentquestion">
+        <xs:element name="commentquestions">
+            <xs:complexType>
+                <xs:choice maxOccurs="unbounded">
+                    <xs:element name="commentquestion" maxOccurs="unbounded" />
+                    <xs:element name="commentradio" maxOccurs="unbounded" />
+                    <xs:element name="commentcheckbox" maxOccurs="unbounded" />
+                    <xs:element name="commentslider" maxOccurs="unbounded" />
+                </xs:choice>
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="commentradio">
             <xs:complexType>
                 <xs:sequence>
-                    <xs:element ref="statement" minOccurs="0" maxOccurs="1" />
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
                     <xs:element name="option" minOccurs="0" maxOccurs="unbounded">
                         <xs:complexType>
                             <xs:simpleContent>
@@ -221,22 +233,202 @@
                 </xs:sequence>
                 <xs:attribute ref="id" use="optional" />
                 <xs:attribute ref="name" use="optional" />
-                <xs:attribute name="type" default="question">
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="commentcheckbox">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                    <xs:element name="option" minOccurs="0" maxOccurs="unbounded">
+                        <xs:complexType>
+                            <xs:simpleContent>
+                                <xs:extension base="xs:string">
+                                    <xs:attribute ref="name" />
+                                </xs:extension>
+                            </xs:simpleContent>
+                        </xs:complexType>
+                    </xs:element>
+                </xs:sequence>
+                <xs:attribute ref="id" use="optional" />
+                <xs:attribute ref="name" use="optional" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="commentquestion">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="optional" />
+                <xs:attribute ref="name" use="optional" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="commentslider">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                    <xs:element name="minText" minOccurs="0" maxOccurs="1" type="xs:string" />
+                    <xs:element name="maxText" minOccurs="0" maxOccurs="1" type="xs:string" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="optional" />
+                <xs:attribute ref="name" use="optional" />
+                <xs:attribute name="min" type="xs:decimal" use="required" />
+                <xs:attribute name="max" type="xs:decimal" use="required" />
+                <xs:attribute name="step" type="xs:decimal" use="optional" default="1" />
+                <xs:attribute name="value" type="xs:decimal" use="optional" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="conditional">
+            <xs:complexType>
+                <xs:attribute name="check" use="required">
                     <xs:simpleType>
                         <xs:restriction base="xs:string">
-                            <xs:enumeration value="question" />
-                            <xs:enumeration value="radio" />
-                            <xs:enumeration value="checkbox" />
+                            <xs:enumeration value="equals" />
+                            <xs:enumeration value="lessThan" />
+                            <xs:enumeration value="greaterThan" />
+                            <xs:enumeration value="stringContains" />
+                        </xs:restriction>
+                    </xs:simpleType>
+                </xs:attribute>
+                <xs:attribute name="value" type="xs:string" use="optional" />
+                <xs:attribute name="jumpToOnPass" type="xs:string" use="optional" />
+                <xs:attribute name="jumpToOnFail" type="xs:string" use="optional" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="surveyquestion">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                    <xs:element ref="conditional" minOccurs="0" maxOccurs="unbounded" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+                <xs:attribute ref="name" />
+                <xs:attribute ref="mandatory" />
+                <xs:attribute name="boxsize" default="normal">
+                    <xs:simpleType>
+                        <xs:restriction base="xs:string">
+                            <xs:enumeration value="normal" />
+                            <xs:enumeration value="large" />
+                            <xs:enumeration value="small" />
+                            <xs:enumeration value="huge" />
                         </xs:restriction>
                     </xs:simpleType>
                 </xs:attribute>
             </xs:complexType>
         </xs:element>
 
+        <xs:element name="surveyradio">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                    <xs:element name="option" minOccurs="0" maxOccurs="unbounded">
+                        <xs:complexType>
+                            <xs:simpleContent>
+                                <xs:extension base="xs:string">
+                                    <xs:attribute ref="name" />
+                                </xs:extension>
+                            </xs:simpleContent>
+                        </xs:complexType>
+                    </xs:element>
+                    <xs:element ref="conditional" minOccurs="0" maxOccurs="unbounded" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+                <xs:attribute ref="name" />
+                <xs:attribute ref="mandatory" />
+                <xs:attribute name="min" type="xs:decimal" />
+                <xs:attribute name="max" type="xs:decimal" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="surveycheckbox">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                    <xs:element name="option" minOccurs="0" maxOccurs="unbounded">
+                        <xs:complexType>
+                            <xs:simpleContent>
+                                <xs:extension base="xs:string">
+                                    <xs:attribute ref="name" />
+                                </xs:extension>
+                            </xs:simpleContent>
+                        </xs:complexType>
+                    </xs:element>
+                    <xs:element ref="conditional" minOccurs="0" maxOccurs="unbounded" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+                <xs:attribute ref="name" />
+                <xs:attribute ref="mandatory" />
+                <xs:attribute name="min" type="xs:decimal" />
+                <xs:attribute name="max" type="xs:decimal" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="surveystatement">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="surveynumber">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                    <xs:element ref="conditional" minOccurs="0" maxOccurs="unbounded" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+                <xs:attribute ref="name" />
+                <xs:attribute ref="mandatory" />
+                <xs:attribute name="min" type="xs:decimal" />
+                <xs:attribute name="max" type="xs:decimal" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="surveyslider">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                    <xs:element name="minText" minOccurs="0" maxOccurs="1" type="xs:string" />
+                    <xs:element name="maxText" minOccurs="0" maxOccurs="1" type="xs:string" />
+                    <xs:element ref="conditional" minOccurs="0" maxOccurs="unbounded" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+                <xs:attribute ref="name" />
+                <xs:attribute name="min" use="required" type="xs:decimal" />
+                <xs:attribute name="max" use="required" type="xs:decimal" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="surveyvideo">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+                <xs:attribute name="url" use="required" type="xs:string" />
+            </xs:complexType>
+        </xs:element>
+
+        <xs:element name="surveyyoutube">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
+                </xs:sequence>
+                <xs:attribute ref="id" use="required" />
+                <xs:attribute name="url" use="required" type="xs:string" />
+            </xs:complexType>
+        </xs:element>
+
         <xs:element name="survey">
             <xs:complexType>
-                <xs:sequence>
-                    <xs:element name="surveyentry" minOccurs="0" maxOccurs="unbounded">
+                <xs:choice maxOccurs="unbounded">
+                    <xs:element name="surveyentry" maxOccurs="unbounded">
                         <xs:complexType>
                             <xs:sequence>
                                 <xs:element ref="statement" minOccurs="1" maxOccurs="1" />
@@ -249,23 +441,7 @@
                                         </xs:simpleContent>
                                     </xs:complexType>
                                 </xs:element>
-                                <xs:element name="conditional" minOccurs="0" maxOccurs="unbounded">
-                                    <xs:complexType>
-                                        <xs:attribute name="check" use="required">
-                                            <xs:simpleType>
-                                                <xs:restriction base="xs:string">
-                                                    <xs:enumeration value="equals" />
-                                                    <xs:enumeration value="lessThan" />
-                                                    <xs:enumeration value="greaterThan" />
-                                                    <xs:enumeration value="stringContains" />
-                                                </xs:restriction>
-                                            </xs:simpleType>
-                                        </xs:attribute>
-                                        <xs:attribute name="value" type="xs:string" use="optional" />
-                                        <xs:attribute name="jumpToOnPass" type="xs:string" use="optional" />
-                                        <xs:attribute name="jumpToOnFail" type="xs:string" use="optional" />
-                                    </xs:complexType>
-                                </xs:element>
+                                <xs:element ref="conditional" minOccurs="0" maxOccurs="unbounded" />
                             </xs:sequence>
                             <xs:attribute ref="id" use="required" />
                             <xs:attribute ref="name" />
@@ -298,7 +474,15 @@
                             <xs:attribute name="url" type="xs:string" use="optional" />
                         </xs:complexType>
                     </xs:element>
-                </xs:sequence>
+                    <xs:element name="surveyquestion" maxOccurs="unbounded" />
+                    <xs:element name="surveyradio" maxOccurs="unbounded" />
+                    <xs:element name="surveycheckbox" maxOccurs="unbounded" />
+                    <xs:element name="surveystatement" maxOccurs="unbounded" />
+                    <xs:element name="surveynumber" maxOccurs="unbounded" />
+                    <xs:element name="surveyslider" maxOccurs="unbounded" />
+                    <xs:element name="surveyvideo" maxOccurs="unbounded" />
+                    <xs:element name="surveyyoutube" maxOccurs="unbounded" />
+                </xs:choice>
                 <xs:attribute name="location">
                     <xs:simpleType>
                         <xs:restriction base="xs:string">