annotate interfaces/ordinal.js @ 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
children be138735977b
rev   line source
nicholas@2844 1 /**
nicholas@2844 2 * WAET Blank Template
nicholas@2844 3 * Use this to start building your custom interface
nicholas@2844 4 */
nicholas@2844 5 /*globals interfaceContext, window, document, specification, audioEngineContext, console, testState, $, storage */
nicholas@2844 6 // Once this is loaded and parsed, begin execution
nicholas@2844 7 loadInterface();
nicholas@2844 8
nicholas@2844 9 function loadInterface() {
nicholas@2844 10 // Use this to do any one-time page / element construction. For instance, placing any stationary text objects,
nicholas@2844 11 // holding div's, or setting up any nodes which are present for the entire test sequence
nicholas@2844 12
nicholas@2844 13 // The injection point into the HTML page
nicholas@2844 14 interfaceContext.insertPoint = document.getElementById("topLevelBody");
nicholas@2844 15 var testContent = document.createElement('div');
nicholas@2844 16 testContent.id = 'testContent';
nicholas@2844 17
nicholas@2844 18 // Create the top div for the Title element
nicholas@2844 19 var titleAttr = specification.title;
nicholas@2844 20 var title = document.createElement('div');
nicholas@2844 21 title.className = "title";
nicholas@2844 22 title.align = "center";
nicholas@2844 23 var titleSpan = document.createElement('span');
nicholas@2844 24 titleSpan.id = "test-title";
nicholas@2844 25
nicholas@2844 26 // Set title to that defined in XML, else set to default
nicholas@2844 27 if (titleAttr !== undefined) {
nicholas@2844 28 titleSpan.textContent = titleAttr;
nicholas@2844 29 } else {
nicholas@2844 30 titleSpan.textContent = 'Listening test';
nicholas@2844 31 }
nicholas@2844 32 // Insert the titleSpan element into the title div element.
nicholas@2844 33 title.appendChild(titleSpan);
nicholas@2844 34
nicholas@2844 35 var pagetitle = document.createElement('div');
nicholas@2844 36 pagetitle.className = "pageTitle";
nicholas@2844 37 pagetitle.align = "center";
nicholas@2844 38
nicholas@2844 39 titleSpan = document.createElement('span');
nicholas@2844 40 titleSpan.id = "pageTitle";
nicholas@2844 41 pagetitle.appendChild(titleSpan);
nicholas@2844 42
nicholas@2844 43 // Create Interface buttons!
nicholas@2844 44 var interfaceButtons = document.createElement('div');
nicholas@2844 45 interfaceButtons.id = 'interface-buttons';
nicholas@2844 46 interfaceButtons.style.height = '25px';
nicholas@2844 47
nicholas@2844 48 // Create playback start/stop points
nicholas@2844 49 var playback = document.createElement("button");
nicholas@2844 50 playback.innerHTML = 'Stop';
nicholas@2844 51 playback.id = 'playback-button';
nicholas@2844 52 playback.style.float = 'left';
nicholas@2844 53 // onclick function. Check if it is playing or not, call the correct function in the
nicholas@2844 54 // audioEngine, change the button text to reflect the next state.
nicholas@2844 55 playback.onclick = function () {
nicholas@2844 56 if (audioEngineContext.status == 1) {
nicholas@2844 57 audioEngineContext.stop();
nicholas@2844 58 this.innerHTML = 'Stop';
nicholas@2844 59 var time = audioEngineContext.timer.getTestTime();
nicholas@2844 60 console.log('Stopped at ' + time); // DEBUG/SAFETY
nicholas@2844 61 }
nicholas@2844 62 };
nicholas@2844 63 // Create Submit (save) button
nicholas@2844 64 var submit = document.createElement("button");
nicholas@2844 65 submit.innerHTML = 'Next';
nicholas@2844 66 submit.onclick = buttonSubmitClick;
nicholas@2844 67 submit.id = 'submit-button';
nicholas@2844 68 submit.style.float = 'left';
nicholas@2844 69 // Append the interface buttons into the interfaceButtons object.
nicholas@2844 70 interfaceButtons.appendChild(playback);
nicholas@2844 71 interfaceButtons.appendChild(submit);
nicholas@2844 72
nicholas@2844 73 // Create outside reference holder
nicholas@2844 74 var outsideRef = document.createElement("div");
nicholas@2844 75 outsideRef.id = "outside-reference-holder";
nicholas@2844 76
nicholas@2844 77 // Create a slider box
nicholas@2844 78 var slider = document.createElement("div");
nicholas@2844 79 slider.id = "slider";
nicholas@2844 80
nicholas@2844 81 // Global parent for the comment boxes on the page
nicholas@2844 82 var feedbackHolder = document.createElement('div');
nicholas@2844 83 feedbackHolder.id = 'feedbackHolder';
nicholas@2844 84
nicholas@2844 85 testContent.style.zIndex = 1;
nicholas@2844 86 interfaceContext.insertPoint.innerHTML = ""; // Clear the current schema
nicholas@2844 87
nicholas@2844 88 // Inject into HTML
nicholas@2844 89 testContent.appendChild(title); // Insert the title
nicholas@2844 90 testContent.appendChild(pagetitle);
nicholas@2844 91 testContent.appendChild(interfaceButtons);
nicholas@2844 92 testContent.appendChild(outsideRef);
nicholas@2844 93 testContent.appendChild(slider);
nicholas@2844 94 testContent.appendChild(feedbackHolder);
nicholas@2844 95 interfaceContext.insertPoint.appendChild(testContent);
nicholas@2844 96
nicholas@2844 97 // Load the full interface
nicholas@2844 98 testState.initialise();
nicholas@2844 99 testState.advanceState();
nicholas@2844 100 }
nicholas@2844 101
nicholas@2844 102 function loadTest(page) {
nicholas@2844 103 // Called each time a new test page is to be build. The page specification node is the only item passed in
nicholas@2844 104 var id = page.id;
nicholas@2844 105
nicholas@2844 106 var feedbackHolder = document.getElementById('feedbackHolder');
nicholas@2844 107 feedbackHolder.innerHTML = "";
nicholas@2844 108
nicholas@2844 109 var interfaceObj = interfaceContext.getCombinedInterfaces(page);
nicholas@2844 110 if (interfaceObj.length > 1) {
nicholas@2844 111 console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
nicholas@2844 112 }
nicholas@2844 113 interfaceObj = interfaceObj[0];
nicholas@2844 114
nicholas@2844 115 // Set the page title
nicholas@2844 116 if (typeof page.title == "string" && page.title.length > 0) {
nicholas@2844 117 document.getElementById("test-title").textContent = page.title;
nicholas@2844 118 }
nicholas@2844 119
nicholas@2844 120 if (interfaceObj.title !== null) {
nicholas@2844 121 document.getElementById("pageTitle").textContent = interfaceObj.title;
nicholas@2844 122 }
nicholas@2844 123
nicholas@2844 124 if (interfaceObj.image !== undefined) {
nicholas@2844 125 feedbackHolder.insertBefore(interfaceContext.imageHolder.root, document.getElementById("slider"));
nicholas@2844 126 interfaceContext.imageHolder.setImage(interfaceObj.image);
nicholas@2844 127 }
nicholas@2844 128 // Delete outside reference
nicholas@2844 129 document.getElementById("outside-reference-holder").innerHTML = "";
nicholas@2844 130
nicholas@2844 131 var sliderBox = document.getElementById('slider');
nicholas@2844 132 sliderBox.innerHTML = "";
nicholas@2844 133
nicholas@2844 134 var commentBoxPrefix = "Comment on track";
nicholas@2844 135 if (interfaceObj.commentBoxPrefix !== undefined) {
nicholas@2844 136 commentBoxPrefix = interfaceObj.commentBoxPrefix;
nicholas@2844 137 }
nicholas@2844 138
nicholas@2844 139 $(page.commentQuestions).each(function (index, element) {
nicholas@2844 140 var node = interfaceContext.createCommentQuestion(element);
nicholas@2844 141 feedbackHolder.appendChild(node.holder);
nicholas@2844 142 });
nicholas@2844 143
nicholas@2844 144 var index = 0;
nicholas@2844 145 var labelType = page.label;
nicholas@2844 146 if (labelType == "default") {
nicholas@2844 147 labelType = "number";
nicholas@2844 148 }
nicholas@2844 149 page.audioElements.forEach(function (element, pageIndex) {
nicholas@2844 150 var audioObject = audioEngineContext.newTrack(element);
nicholas@2844 151 if (element.type == 'outside-reference') {
nicholas@2844 152 // Construct outside reference;
nicholas@2844 153 var orNode = new interfaceContext.outsideReferenceDOM(audioObject, index, document.getElementById("outside-reference-holder"));
nicholas@2844 154 audioObject.bindInterface(orNode);
nicholas@2844 155 } else {
nicholas@2844 156 // Create a slider per track
nicholas@2844 157 var label = interfaceContext.getLabel(labelType, index, page.labelStart);
nicholas@2844 158 var sliderObj = new interfaceObject(audioObject, label);
nicholas@2844 159
nicholas@2844 160 sliderBox.appendChild(sliderObj.root);
nicholas@2844 161 audioObject.bindInterface(sliderObj);
nicholas@2844 162 interfaceContext.commentBoxes.createCommentBox(audioObject);
nicholas@2844 163 index += 1;
nicholas@2844 164 }
nicholas@2844 165 });
nicholas@2844 166 resizeWindow();
nicholas@2844 167 }
nicholas@2844 168
nicholas@2844 169 function interfaceObject(audioObject, label) {
nicholas@2844 170 var container = document.getElementById("slider");
nicholas@2844 171 var playing = false;
nicholas@2844 172 var root = document.createElement("div");
nicholas@2844 173 root.className = "ordinal-element";
nicholas@2844 174 root.draggable = "true";
nicholas@2844 175 var labelElement = document.createElement("span");
nicholas@2844 176 labelElement.className = "ordinal-element-label";
nicholas@2844 177 labelElement.textContent = label;
nicholas@2844 178 root.appendChild(labelElement);
nicholas@2844 179 root.classList.add("disabled");
nicholas@2844 180 // An example node, you can make this however you want for each audioElement.
nicholas@2844 181 // However, every audioObject (audioEngineContext.audioObject) MUST have an interface object with the following
nicholas@2844 182 // You attach them by calling audioObject.bindInterface( )
nicholas@2844 183 root.addEventListener("click", this, true);
nicholas@2844 184 root.addEventListener('dragstart', this, true);
nicholas@2844 185 root.addEventListener('dragenter', this, true);
nicholas@2844 186 root.addEventListener('dragover', this, true);
nicholas@2844 187 root.addEventListener('dragleave', this, true);
nicholas@2844 188 root.addEventListener('drop', this, true);
nicholas@2844 189 root.addEventListener('dragend', this, true);
nicholas@2844 190 this.handleEvent = function (event) {
nicholas@2844 191 if (event.type == "click") {
nicholas@2844 192 if (playing === false) {
nicholas@2844 193 audioEngineContext.play(audioObject.id);
nicholas@2844 194 } else {
nicholas@2844 195 audioEngineContext.stop();
nicholas@2844 196 }
nicholas@2844 197 playing = !playing;
nicholas@2844 198 return;
nicholas@2844 199 } else if (event.type == "dragstart") {
nicholas@2844 200 return dragStart.call(this, event);
nicholas@2844 201 } else if (event.type == "dragenter") {
nicholas@2844 202 return dragEnter.call(this, event);
nicholas@2844 203 } else if (event.type == "dragleave") {
nicholas@2844 204 return dragLeave.call(this, event);
nicholas@2844 205 } else if (event.type == "dragover") {
nicholas@2844 206 return dragOver.call(this, event);
nicholas@2844 207 } else if (event.type == "drop") {
nicholas@2844 208 return drop.call(this, event);
nicholas@2844 209 } else if (event.type == "dragend") {
nicholas@2844 210 return dragEnd.call(this, event);
nicholas@2844 211 }
nicholas@2844 212 throw (event);
nicholas@2844 213 };
nicholas@2844 214
nicholas@2844 215 function dragStart(e) {
nicholas@2844 216 e.currentTarget.classList.add("dragging");
nicholas@2844 217
nicholas@2844 218 e.dataTransfer.effectAllowed = 'move';
nicholas@2844 219 e.dataTransfer.setData('text/plain', audioObject.id);
nicholas@2844 220 }
nicholas@2844 221
nicholas@2844 222 function dragEnter(e) {
nicholas@2844 223 // this / e.target is the current hover target.
nicholas@2844 224 root.classList.add('over');
nicholas@2844 225 }
nicholas@2844 226
nicholas@2844 227 function dragLeave(e) {
nicholas@2844 228 root.classList.remove('over'); // this / e.target is previous target element.
nicholas@2844 229 }
nicholas@2844 230
nicholas@2844 231 function dragOver(e) {
nicholas@2844 232 if (e.preventDefault) {
nicholas@2844 233 e.preventDefault(); // Necessary. Allows us to drop.
nicholas@2844 234 }
nicholas@2844 235
nicholas@2844 236 e.dataTransfer.dropEffect = 'move'; // See the section on the DataTransfer object.
nicholas@2844 237
nicholas@2844 238 var srcid = Number(e.dataTransfer.getData("text/plain"));
nicholas@2844 239 var elements = container.childNodes;
nicholas@2844 240 var srcObject = audioEngineContext.audioObjects.find(function (ao) {
nicholas@2844 241 return ao.id === srcid;
nicholas@2844 242 });
nicholas@2844 243 var src = srcObject.interfaceDOM.root;
nicholas@2844 244 if (src !== root) {
nicholas@2844 245 var srcpos = srcObject.interfaceDOM.getElementPosition();
nicholas@2844 246 var mypos = this.getElementPosition();
nicholas@2844 247 var neighbour;
nicholas@2844 248 if (srcpos <= mypos) {
nicholas@2844 249 neighbour = root.nextElementSibling;
nicholas@2844 250 } else {
nicholas@2844 251 neighbour = root;
nicholas@2844 252 }
nicholas@2844 253 if (neighbour)
nicholas@2844 254 container.insertBefore(src, neighbour);
nicholas@2844 255 else {
nicholas@2844 256 container.removeChild(src);
nicholas@2844 257 container.appendChild(src);
nicholas@2844 258 }
nicholas@2844 259
nicholas@2844 260 }
nicholas@2844 261
nicholas@2844 262 return false;
nicholas@2844 263 }
nicholas@2844 264
nicholas@2844 265 function drop(e) {
nicholas@2844 266 // this / e.target is current target element.
nicholas@2844 267
nicholas@2844 268 if (e.stopPropagation) {
nicholas@2844 269 e.stopPropagation(); // stops the browser from redirecting.
nicholas@2844 270 }
nicholas@2844 271 if (e.preventDefault) {
nicholas@2844 272 e.preventDefault(); // Necessary. Allows us to drop.
nicholas@2844 273 }
nicholas@2844 274
nicholas@2844 275 audioEngineContext.audioObjects.forEach(function (ao) {
nicholas@2844 276 ao.interfaceDOM.processMovement();
nicholas@2844 277 });
nicholas@2844 278
nicholas@2844 279 return false;
nicholas@2844 280 }
nicholas@2844 281
nicholas@2844 282 function dragEnd(e) {
nicholas@2844 283 // this/e.target is the source node.
nicholas@2844 284 $(".ordinal-element").removeClass("dragging");
nicholas@2844 285 $(".ordinal-element").removeClass("over");
nicholas@2844 286 }
nicholas@2844 287
nicholas@2844 288 this.getElementPosition = function () {
nicholas@2844 289 var elements = container.childNodes,
nicholas@2844 290 position = 0,
nicholas@2844 291 elem = elements[0];
nicholas@2844 292 while (root !== elem) {
nicholas@2844 293 position++;
nicholas@2844 294 elem = elem.nextElementSibling;
nicholas@2844 295 }
nicholas@2844 296 return position;
nicholas@2844 297 }
nicholas@2844 298
nicholas@2844 299 this.processMovement = function () {
nicholas@2844 300 var time = audioEngineContext.timer.getTestTime();
nicholas@2844 301 var pos = this.getElementPosition();
nicholas@2844 302 var rank = pos / (audioEngineContext.audioObjects.length - 1);
nicholas@2844 303 audioObject.metric.moved(time, rank);
nicholas@2844 304 console.log('slider ' + audioObject.id + ' moved to ' + rank + ' (' + time + ')');
nicholas@2844 305 }
nicholas@2844 306
nicholas@2844 307 this.enable = function () {
nicholas@2844 308 // This is used to tell the interface object that playback of this node is ready
nicholas@2844 309 root.classList.remove("disabled");
nicholas@2844 310 labelElement.textContent = label;
nicholas@2844 311 };
nicholas@2844 312 this.updateLoading = function (progress) {
nicholas@2844 313 // progress is a value from 0 to 100 indicating the current download state of media files
nicholas@2844 314 labelElement.textContent = String(progress);
nicholas@2844 315 };
nicholas@2844 316 this.startPlayback = function () {
nicholas@2844 317 // Called when playback has begun
nicholas@2844 318 root.classList.add("playing");
nicholas@2844 319 };
nicholas@2844 320 this.stopPlayback = function () {
nicholas@2844 321 // Called when playback has stopped. This gets called even if playback never started!
nicholas@2844 322 root.classList.remove("playing");
nicholas@2844 323 };
nicholas@2844 324 this.getValue = function () {
nicholas@2844 325 // Return the current value of the object. If there is no value, return 0
nicholas@2844 326 var pos = this.getElementPosition();
nicholas@2844 327 var rank = pos / (audioEngineContext.audioObjects.length - 1);
nicholas@2844 328 };
nicholas@2844 329 this.getPresentedId = function () {
nicholas@2844 330 // Return the presented ID of the object. For instance, the APE has sliders starting from 0. Whilst AB has alphabetical scale
nicholas@2844 331 return label;
nicholas@2844 332 };
nicholas@2844 333 this.canMove = function () {
nicholas@2844 334 // Return either true or false if the interface object can be moved. AB / Reference cannot, whilst sliders can and therefore have a continuous scale.
nicholas@2844 335 // These are checked primarily if the interface check option 'fragmentMoved' is enabled.
nicholas@2844 336 return true;
nicholas@2844 337 };
nicholas@2844 338 this.exportXMLDOM = function (audioObject) {
nicholas@2844 339 // Called by the audioObject holding this element to export the interface <value> node.
nicholas@2844 340 // If there is no value node (such as outside reference), return null
nicholas@2844 341 // 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
nicholas@2844 342 // Use storage.document.createElement('value'); to generate the XML node.
nicholas@2844 343 var node = storage.document.createElement('value');
nicholas@2844 344 node.textContent = this.slider.value;
nicholas@2844 345 return node;
nicholas@2844 346
nicholas@2844 347 };
nicholas@2844 348 this.error = function () {
nicholas@2844 349 // If there is an error with the audioObject, this will be called to indicate a failure
nicholas@2844 350 root.classList.remove("disabled");
nicholas@2844 351 labelElement.textContent = "Error";
nicholas@2844 352 };
nicholas@2844 353 Object.defineProperties(this, {
nicholas@2844 354 "root": {
nicholas@2844 355 "get": function () {
nicholas@2844 356 return root;
nicholas@2844 357 },
nicholas@2844 358 "set": function () {}
nicholas@2844 359 }
nicholas@2844 360 });
nicholas@2844 361 }
nicholas@2844 362
nicholas@2844 363 function resizeWindow(event) {
nicholas@2844 364 // Called on every window resize event, use this to scale your page properly
nicholas@2844 365 var w = $("#slider").width();
nicholas@2844 366 var N = audioEngineContext.audioObjects.length;
nicholas@2844 367 w /= N;
nicholas@2844 368 w -= 14;
nicholas@2844 369 $(".ordinal-element").width(w);
nicholas@2844 370 }
nicholas@2844 371
nicholas@2844 372 function buttonSubmitClick() {
nicholas@2844 373
nicholas@2844 374 }
nicholas@2844 375
nicholas@2844 376 function pageXMLSave(store, pageSpecification) {
nicholas@2844 377 // MANDATORY
nicholas@2844 378 // Saves a specific test page
nicholas@2844 379 // You can use this space to add any extra nodes to your XML <audioHolder> saves
nicholas@2844 380 // Get the current <page> information in store (remember to appendChild your data to it)
nicholas@2844 381 // pageSpecification is the current page node configuration
nicholas@2844 382 // To create new XML nodes, use storage.document.createElement();
nicholas@2844 383 }