changeset 2844:f7ea4470bdd6

#141 Initial commit for ordinal evaluation
author Nicholas Jillings <nicholas.jillings@mail.bcu.ac.uk>
date Tue, 25 Apr 2017 15:27:52 +0100
parents dae4f650193b
children be138735977b
files interfaces/interfaces.json interfaces/ordinal.css interfaces/ordinal.js
diffstat 3 files changed, 423 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/interfaces/interfaces.json	Tue Apr 25 12:59:41 2017 +0100
+++ b/interfaces/interfaces.json	Tue Apr 25 15:27:52 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"]
         }
     ]
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interfaces/ordinal.css	Tue Apr 25 15:27:52 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	Tue Apr 25 15:27:52 2017 +0100
@@ -0,0 +1,383 @@
+/**
+ * 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";
+
+    // 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;
+        }
+    });
+    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");
+    };
+    this.stopPlayback = function () {
+        // Called when playback has stopped. This gets called even if playback never started!
+        root.classList.remove("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.slider.value;
+        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;
+    $(".ordinal-element").width(w);
+}
+
+function buttonSubmitClick() {
+
+}
+
+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();
+}