annotate ape.js @ 1610:63217e7bd1a7

Don't allow to submit before all songs are played (slightly hacky patch)
author Brecht De Man <b.deman@qmul.ac.uk>
date Sun, 17 May 2015 09:00:09 +0100
parents 590e487fe3f1
children 36d5c460f04b
rev   line source
b@1608 1 /**
b@1608 2 * ape.js
b@1608 3 * Create the APE interface
b@1608 4 */
b@1608 5
b@1608 6 var currentState; // Keep track of the current state (pre/post test, which test, final test? first test?)
b@1608 7 // preTest - In preTest state
b@1608 8 // testRun-ID - In test running, test Id number at the end 'testRun-2'
b@1608 9 // testRunPost-ID - Post test of test ID
b@1608 10 // testRunPre-ID - Pre-test of test ID
b@1608 11 // postTest - End of test, final submission!
b@1608 12
b@1608 13
b@1610 14 // Create empty array to log whether different samples have been played
b@1610 15 var hasBeenPlayed = []; // HERE?
b@1610 16
b@1610 17
b@1608 18 // Once this is loaded and parsed, begin execution
b@1608 19 loadInterface(projectXML);
b@1608 20
b@1608 21 function loadInterface(xmlDoc) {
b@1608 22
b@1608 23 // Get the dimensions of the screen available to the page
b@1608 24 var width = window.innerWidth;
b@1608 25 var height = window.innerHeight;
b@1608 26
b@1608 27 // The injection point into the HTML page
b@1608 28 var insertPoint = document.getElementById("topLevelBody");
b@1608 29 var testContent = document.createElement('div');
b@1608 30 testContent.id = 'testContent';
b@1608 31
b@1608 32
b@1608 33 // Decode parts of the xmlDoc that are needed
b@1608 34 // xmlDoc MUST already be parsed by jQuery!
b@1608 35 var xmlSetup = xmlDoc.find('setup');
b@1608 36 // Should put in an error function here incase of malprocessed or malformed XML
b@1608 37
b@1608 38 // Extract the different test XML DOM trees
b@1608 39 var audioHolders = xmlDoc.find('audioHolder');
b@1608 40 audioHolders.each(function(index,element) {
b@1608 41 var repeatN = element.attributes['repeatCount'].value;
b@1608 42 for (var r=0; r<=repeatN; r++) {
b@1608 43 testXMLSetups[testXMLSetups.length] = element;
b@1608 44 }
b@1608 45 });
b@1608 46
b@1608 47 // New check if we need to randomise the test order
b@1608 48 var randomise = xmlSetup[0].attributes['randomiseOrder'];
b@1608 49 if (randomise != undefined) {
b@1608 50 randomise = Boolean(randomise.value);
b@1608 51 } else {
b@1608 52 randomise = false;
b@1608 53 }
b@1608 54 if (randomise)
b@1608 55 {
b@1608 56 testXMLSetups = randomiseOrder(testXMLSetups);
b@1608 57 }
b@1608 58
b@1608 59 // Obtain the metrics enabled
b@1608 60 var metricNode = xmlSetup.find('Metric');
b@1608 61 var metricNode = metricNode.find('metricEnable');
b@1608 62 metricNode.each(function(index,node){
b@1608 63 var enabled = node.textContent;
b@1608 64 switch(enabled)
b@1608 65 {
b@1608 66 case 'testTimer':
b@1608 67 sessionMetrics.prototype.enableTestTimer = true;
b@1608 68 break;
b@1608 69 case 'elementTimer':
b@1608 70 sessionMetrics.prototype.enableElementTimer = true;
b@1608 71 break;
b@1608 72 case 'elementTracker':
b@1608 73 sessionMetrics.prototype.enableElementTracker = true;
b@1608 74 break;
b@1608 75 case 'elementInitalPosition':
b@1608 76 sessionMetrics.prototype.enableElementInitialPosition = true;
b@1608 77 break;
b@1608 78 case 'elementFlagListenedTo':
b@1608 79 sessionMetrics.prototype.enableFlagListenedTo = true;
b@1608 80 break;
b@1608 81 case 'elementFlagMoved':
b@1608 82 sessionMetrics.prototype.enableFlagMoved = true;
b@1608 83 break;
b@1608 84 case 'elementFlagComments':
b@1608 85 sessionMetrics.prototype.enableFlagComments = true;
b@1608 86 break;
b@1608 87 }
b@1608 88 });
b@1608 89
b@1608 90 // Create APE specific metric functions
b@1608 91 audioEngineContext.metric.initialiseTest = function()
b@1608 92 {
b@1608 93 var sliders = document.getElementsByClassName('track-slider');
b@1608 94 for (var i=0; i<sliders.length; i++)
b@1608 95 {
b@1608 96 audioEngineContext.audioObjects[i].metric.initialised(convSliderPosToRate(i));
b@1608 97 }
b@1608 98 };
b@1608 99
b@1608 100 audioEngineContext.metric.sliderMoveStart = function(id)
b@1608 101 {
b@1608 102 if (this.data == -1)
b@1608 103 {
b@1608 104 this.data = id;
b@1608 105 } else {
b@1608 106 console.log('ERROR: Metric tracker detecting two moves!');
b@1608 107 this.data = -1;
b@1608 108 }
b@1608 109 };
b@1608 110 audioEngineContext.metric.sliderMoved = function()
b@1608 111 {
b@1608 112 var time = audioEngineContext.timer.getTestTime();
b@1608 113 var id = this.data;
b@1608 114 this.data = -1;
b@1608 115 var position = convSliderPosToRate(id);
b@1608 116 if (audioEngineContext.timer.testStarted)
b@1608 117 {
b@1608 118 audioEngineContext.audioObjects[id].metric.moved(time,position);
b@1608 119 }
b@1608 120 };
b@1608 121
b@1608 122 audioEngineContext.metric.sliderPlayed = function(id)
b@1608 123 {
b@1608 124 var time = audioEngineContext.timer.getTestTime();
b@1608 125 if (audioEngineContext.timer.testStarted)
b@1608 126 {
b@1608 127 if (this.lastClicked >= 0)
b@1608 128 {
b@1608 129 audioEngineContext.audioObjects[this.lastClicked].metric.listening(time);
b@1608 130 }
b@1608 131 this.lastClicked = id;
b@1608 132 audioEngineContext.audioObjects[id].metric.listening(time);
b@1608 133 }
b@1608 134 };
b@1608 135
b@1608 136 // Create the top div for the Title element
b@1608 137 var titleAttr = xmlSetup[0].attributes['title'];
b@1608 138 var title = document.createElement('div');
b@1608 139 title.className = "title";
b@1608 140 title.align = "center";
b@1608 141 var titleSpan = document.createElement('span');
b@1608 142
b@1608 143 // Set title to that defined in XML, else set to default
b@1608 144 if (titleAttr != undefined) {
b@1608 145 titleSpan.innerHTML = titleAttr.value;
b@1608 146 } else {
b@1608 147 titleSpan.innerHTML = 'Listening test';
b@1608 148 }
b@1608 149 // Insert the titleSpan element into the title div element.
b@1608 150 title.appendChild(titleSpan);
b@1608 151
b@1608 152 var pagetitle = document.createElement('div');
b@1608 153 pagetitle.className = "pageTitle";
b@1608 154 pagetitle.align = "center";
b@1608 155 var titleSpan = document.createElement('span');
b@1608 156 titleSpan.id = "pageTitle";
b@1608 157 pagetitle.appendChild(titleSpan);
b@1608 158
b@1608 159 // Store the return URL path in global projectReturn
b@1608 160 projectReturn = xmlSetup[0].attributes['projectReturn'].value;
b@1608 161
b@1608 162 // Create Interface buttons!
b@1608 163 var interfaceButtons = document.createElement('div');
b@1608 164 interfaceButtons.id = 'interface-buttons';
b@1608 165
b@1608 166 // MANUAL DOWNLOAD POINT
b@1608 167 // If project return is null, this MUST be specified as the location to create the download link
b@1608 168 var downloadPoint = document.createElement('div');
b@1608 169 downloadPoint.id = 'download-point';
b@1608 170
b@1608 171 // Create playback start/stop points
b@1608 172 var playback = document.createElement("button");
b@1609 173 playback.innerHTML = 'Stop';
b@1608 174 playback.id = 'playback-button';
b@1608 175 // onclick function. Check if it is playing or not, call the correct function in the
b@1608 176 // audioEngine, change the button text to reflect the next state.
b@1608 177 playback.onclick = function() {
b@1609 178 if (audioEngineContext.status == 1) {
b@1609 179 audioEngineContext.stop();
b@1608 180 this.innerHTML = 'Stop';
b@1608 181 }
b@1608 182 };
b@1608 183 // Create Submit (save) button
b@1608 184 var submit = document.createElement("button");
b@1608 185 submit.innerHTML = 'Submit';
b@1608 186 submit.onclick = buttonSubmitClick;
b@1608 187 submit.id = 'submit-button';
b@1608 188 // Append the interface buttons into the interfaceButtons object.
b@1608 189 interfaceButtons.appendChild(playback);
b@1608 190 interfaceButtons.appendChild(submit);
b@1608 191 interfaceButtons.appendChild(downloadPoint);
b@1608 192
b@1608 193 // Now create the slider and HTML5 canvas boxes
b@1608 194
b@1608 195 // Create the div box to center align
b@1608 196 var sliderBox = document.createElement('div');
b@1608 197 sliderBox.className = 'sliderCanvasDiv';
b@1608 198 sliderBox.id = 'sliderCanvasHolder';
b@1608 199 sliderBox.align = 'center';
b@1608 200
b@1608 201 // Create the slider box to hold the slider elements
b@1608 202 var canvas = document.createElement('div');
b@1608 203 canvas.id = 'slider';
b@1608 204 // Must have a known EXACT width, as this is used later to determine the ratings
b@1608 205 canvas.style.width = width - 100 +"px";
b@1608 206 canvas.align = "left";
b@1608 207 sliderBox.appendChild(canvas);
b@1608 208
b@1608 209 // Create the div to hold any scale objects
b@1608 210 var scale = document.createElement('div');
b@1608 211 scale.className = 'sliderScale';
b@1608 212 scale.id = 'sliderScaleHolder';
b@1608 213 scale.align = 'left';
b@1608 214 sliderBox.appendChild(scale);
b@1608 215
b@1608 216 // Global parent for the comment boxes on the page
b@1608 217 var feedbackHolder = document.createElement('div');
b@1608 218 feedbackHolder.id = 'feedbackHolder';
b@1608 219
b@1608 220 testContent.style.zIndex = 1;
b@1608 221 insertPoint.innerHTML = null; // Clear the current schema
b@1608 222
b@1608 223 // Create pre and post test questions
b@1608 224 var blank = document.createElement('div');
b@1608 225 blank.className = 'testHalt';
b@1608 226
b@1608 227 var popupHolder = document.createElement('div');
b@1608 228 popupHolder.id = 'popupHolder';
b@1608 229 popupHolder.className = 'popupHolder';
b@1608 230 popupHolder.style.position = 'absolute';
b@1608 231 popupHolder.style.left = (window.innerWidth/2)-250 + 'px';
b@1608 232 popupHolder.style.top = (window.innerHeight/2)-125 + 'px';
b@1608 233 insertPoint.appendChild(popupHolder);
b@1608 234 insertPoint.appendChild(blank);
b@1608 235 hidePopup();
b@1608 236
b@1608 237 var preTest = xmlSetup.find('PreTest');
b@1608 238 var postTest = xmlSetup.find('PostTest');
b@1608 239 preTest = preTest[0];
b@1608 240 postTest = postTest[0];
b@1608 241
b@1608 242 currentState = 'preTest';
b@1608 243
b@1608 244 // Create Pre-Test Box
b@1608 245 if (preTest != undefined && preTest.children.length >= 1)
b@1608 246 {
b@1608 247 showPopup();
b@1608 248 preTestPopupStart(preTest);
b@1608 249 }
b@1608 250
b@1608 251 // Inject into HTML
b@1608 252 testContent.appendChild(title); // Insert the title
b@1608 253 testContent.appendChild(pagetitle);
b@1608 254 testContent.appendChild(interfaceButtons);
b@1608 255 testContent.appendChild(sliderBox);
b@1608 256 testContent.appendChild(feedbackHolder);
b@1608 257 insertPoint.appendChild(testContent);
b@1608 258
b@1608 259 // Load the full interface
b@1608 260
b@1608 261 }
b@1608 262
b@1608 263 function loadTest(id)
b@1608 264 {
b@1608 265 // Used to load a specific test page
b@1608 266 var textXML = testXMLSetups[id];
b@1608 267
b@1608 268 var feedbackHolder = document.getElementById('feedbackHolder');
b@1608 269 var canvas = document.getElementById('slider');
b@1608 270 feedbackHolder.innerHTML = null;
b@1608 271 canvas.innerHTML = null;
b@1608 272
b@1608 273 // Setup question title
b@1608 274 var interfaceObj = $(textXML).find('interface');
b@1608 275 var titleNode = interfaceObj.find('title');
b@1608 276 if (titleNode[0] != undefined)
b@1608 277 {
b@1608 278 document.getElementById('pageTitle').textContent = titleNode[0].textContent;
b@1608 279 }
b@1608 280 var positionScale = canvas.style.width.substr(0,canvas.style.width.length-2);
b@1608 281 var offset = 50-8; // Half the offset of the slider (window width -100) minus the body padding of 8
b@1608 282 // TODO: AUTOMATE ABOVE!!
b@1608 283 var scale = document.getElementById('sliderScaleHolder');
b@1608 284 scale.innerHTML = null;
b@1608 285 interfaceObj.find('scale').each(function(index,scaleObj){
b@1608 286 var position = Number(scaleObj.attributes['position'].value)*0.01;
b@1608 287 var pixelPosition = (position*positionScale)+offset;
b@1608 288 var scaleDOM = document.createElement('span');
b@1608 289 scaleDOM.textContent = scaleObj.textContent;
b@1608 290 scale.appendChild(scaleDOM);
b@1608 291 scaleDOM.style.left = Math.floor((pixelPosition-($(scaleDOM).width()/2)))+'px';
b@1608 292 });
b@1608 293
b@1608 294 // Extract the hostURL attribute. If not set, create an empty string.
b@1608 295 var hostURL = textXML.attributes['hostURL'];
b@1608 296 if (hostURL == undefined) {
b@1608 297 hostURL = "";
b@1608 298 } else {
b@1608 299 hostURL = hostURL.value;
b@1608 300 }
b@1608 301 // Extract the sampleRate. If set, convert the string to a Number.
b@1608 302 var hostFs = textXML.attributes['sampleRate'];
b@1608 303 if (hostFs != undefined) {
b@1608 304 hostFs = Number(hostFs.value);
b@1608 305 }
b@1608 306
b@1608 307 /// CHECK FOR SAMPLE RATE COMPATIBILITY
b@1608 308 if (hostFs != undefined) {
b@1608 309 if (Number(hostFs) != audioContext.sampleRate) {
b@1608 310 var errStr = 'Sample rates do not match! Requested '+Number(hostFs)+', got '+audioContext.sampleRate+'. Please set the sample rate to match before completing this test.';
b@1608 311 alert(errStr);
b@1608 312 return;
b@1608 313 }
b@1608 314 }
b@1608 315
b@1608 316 var commentShow = textXML.attributes['elementComments'];
b@1608 317 if (commentShow != undefined) {
b@1608 318 if (commentShow.value == 'false') {commentShow = false;}
b@1608 319 else {commentShow = true;}
b@1608 320 } else {commentShow = true;}
b@1608 321
b@1608 322 var loopPlayback = textXML.attributes['loop'];
b@1608 323 if (loopPlayback != undefined)
b@1608 324 {
b@1608 325 loopPlayback = loopPlayback.value;
b@1608 326 if (loopPlayback == 'true') {
b@1608 327 loopPlayback = true;
b@1608 328 } else {
b@1608 329 loopPlayback = false;
b@1608 330 }
b@1608 331 } else {
b@1608 332 loopPlayback = false;
b@1608 333 }
b@1608 334 audioEngineContext.loopPlayback = loopPlayback;
b@1608 335
b@1608 336 // Create AudioEngine bindings for playback
b@1608 337 if (loopPlayback) {
b@1608 338 audioEngineContext.play = function() {
b@1608 339 // Send play command to all playback buffers for synchronised start
b@1608 340 // Also start timer callbacks to detect if playback has finished
b@1608 341 if (this.status == 0) {
b@1608 342 this.timer.startTest();
b@1608 343 // First get current clock
b@1608 344 var timer = audioContext.currentTime;
b@1608 345 // Add 3 seconds
b@1608 346 timer += 3.0;
b@1608 347 // Send play to all tracks
b@1608 348 for (var i=0; i<this.audioObjects.length; i++)
b@1608 349 {
b@1608 350 this.audioObjects[i].play(timer);
b@1608 351 }
b@1608 352 this.status = 1;
b@1608 353 }
b@1608 354 };
b@1608 355
b@1608 356 audioEngineContext.stop = function() {
b@1608 357 // Send stop and reset command to all playback buffers
b@1608 358 if (this.status == 1) {
b@1608 359 if (this.loopPlayback) {
b@1608 360 for (var i=0; i<this.audioObjects.length; i++)
b@1608 361 {
b@1608 362 this.audioObjects[i].stop();
b@1608 363 }
b@1608 364 }
b@1608 365 this.status = 0;
b@1608 366 }
b@1608 367 };
b@1608 368
b@1608 369 audioEngineContext.selectedTrack = function(id) {
b@1608 370 for (var i=0; i<this.audioObjects.length; i++)
b@1608 371 {
b@1608 372 if (id == i) {
b@1608 373 this.audioObjects[i].outputGain.gain.value = 1.0;
b@1608 374 } else {
b@1608 375 this.audioObjects[i].outputGain.gain.value = 0.0;
b@1608 376 }
b@1608 377 }
b@1608 378 };
b@1608 379 } else {
b@1608 380 audioEngineContext.play = function() {
b@1608 381 // Send play command to all playback buffers for synchronised start
b@1608 382 // Also start timer callbacks to detect if playback has finished
b@1608 383 if (this.status == 0) {
b@1608 384 this.timer.startTest();
b@1608 385 this.status = 1;
b@1608 386 }
b@1608 387 };
b@1608 388
b@1608 389 audioEngineContext.stop = function() {
b@1608 390 // Send stop and reset command to all playback buffers
b@1608 391 if (this.status == 1) {
b@1608 392 if (this.loopPlayback) {
b@1608 393 for (var i=0; i<this.audioObjects.length; i++)
b@1608 394 {
b@1608 395 this.audioObjects[i].stop();
b@1608 396 }
b@1608 397 }
b@1608 398 this.status = 0;
b@1608 399 }
b@1608 400 };
b@1608 401
b@1608 402 audioEngineContext.selectedTrack = function(id) {
b@1608 403 for (var i=0; i<this.audioObjects.length; i++)
b@1608 404 {
b@1608 405 if (id == i) {
b@1608 406 this.audioObjects[i].outputGain.gain.value = 1.0;
b@1608 407 this.audioObjects[i].play(audioContext.currentTime+0.01);
b@1608 408 } else {
b@1608 409 this.audioObjects[i].outputGain.gain.value = 0.0;
b@1608 410 this.audioObjects[i].stop();
b@1608 411 }
b@1608 412 }
b@1608 413 };
b@1608 414 }
b@1608 415
b@1608 416 currentTestHolder = document.createElement('audioHolder');
b@1608 417 currentTestHolder.id = textXML.id;
b@1608 418 currentTestHolder.repeatCount = textXML.attributes['repeatCount'].value;
b@1608 419 var currentPreTestHolder = document.createElement('preTest');
b@1608 420 var currentPostTestHolder = document.createElement('postTest');
b@1608 421 currentTestHolder.appendChild(currentPreTestHolder);
b@1608 422 currentTestHolder.appendChild(currentPostTestHolder);
b@1608 423
b@1608 424 var randomise = textXML.attributes['randomiseOrder'];
b@1608 425 if (randomise != undefined) {randomise = randomise.value;}
b@1608 426 else {randomise = false;}
b@1608 427
b@1608 428 var audioElements = $(textXML).find('audioElements');
b@1608 429 currentTrackOrder = [];
b@1608 430 audioElements.each(function(index,element){
b@1608 431 // Find any blind-repeats
b@1608 432 // Not implemented yet, but just in case
b@1608 433 currentTrackOrder[index] = element;
b@1608 434 });
b@1608 435 if (randomise) {
b@1608 436 currentTrackOrder = randomiseOrder(currentTrackOrder);
b@1608 437 }
b@1608 438
b@1608 439 // Delete any previous audioObjects associated with the audioEngine
b@1608 440 audioEngineContext.audioObjects = [];
b@1608 441
b@1608 442 // Find all the audioElements from the audioHolder
b@1608 443 $(currentTrackOrder).each(function(index,element){
b@1608 444 // Find URL of track
b@1608 445 // In this jQuery loop, variable 'this' holds the current audioElement.
b@1608 446
b@1608 447 // Now load each audio sample. First create the new track by passing the full URL
b@1608 448 var trackURL = hostURL + this.attributes['url'].value;
b@1608 449 audioEngineContext.newTrack(trackURL);
b@1608 450
b@1608 451 if (commentShow) {
b@1608 452 // Create document objects to hold the comment boxes
b@1608 453 var trackComment = document.createElement('div');
b@1608 454 trackComment.className = 'comment-div';
b@1608 455 // Create a string next to each comment asking for a comment
b@1608 456 var trackString = document.createElement('span');
b@1608 457 trackString.innerHTML = 'Comment on track '+index;
b@1608 458 // Create the HTML5 comment box 'textarea'
b@1608 459 var trackCommentBox = document.createElement('textarea');
b@1608 460 trackCommentBox.rows = '4';
b@1608 461 trackCommentBox.cols = '100';
b@1608 462 trackCommentBox.name = 'trackComment'+index;
b@1608 463 trackCommentBox.className = 'trackComment';
b@1608 464 var br = document.createElement('br');
b@1608 465 // Add to the holder.
b@1608 466 trackComment.appendChild(trackString);
b@1608 467 trackComment.appendChild(br);
b@1608 468 trackComment.appendChild(trackCommentBox);
b@1608 469 feedbackHolder.appendChild(trackComment);
b@1608 470 }
b@1608 471
b@1608 472 // Create a slider per track
b@1608 473
b@1608 474 var trackSliderObj = document.createElement('div');
b@1608 475 trackSliderObj.className = 'track-slider';
b@1608 476 trackSliderObj.id = 'track-slider-'+index;
b@1608 477 // Distribute it randomnly
b@1608 478 var w = window.innerWidth - 100;
b@1608 479 w = Math.random()*w;
b@1608 480 trackSliderObj.style.left = Math.floor(w)+50+'px';
b@1608 481 trackSliderObj.innerHTML = '<span>'+index+'</span>';
b@1608 482 trackSliderObj.draggable = true;
b@1608 483 trackSliderObj.ondragend = dragEnd;
b@1608 484 trackSliderObj.ondragstart = function()
b@1608 485 {
b@1608 486 var id = Number(this.id.substr(13,2)); // Maximum theoretical tracks is 99!
b@1608 487 audioEngineContext.metric.sliderMoveStart(id);
b@1608 488 };
b@1608 489
b@1608 490 // Onclick, switch playback to that track
b@1608 491 trackSliderObj.onclick = function() {
b@1608 492 // Get the track ID from the object ID
b@1608 493 var id = Number(this.id.substr(13,2)); // Maximum theoretical tracks is 99!
b@1608 494 audioEngineContext.metric.sliderPlayed(id);
b@1608 495 audioEngineContext.selectedTrack(id);
b@1608 496 // Currently playing track red, rest green
b@1608 497 document.getElementById('track-slider-'+index).style.backgroundColor = "#FF0000";
b@1608 498 for (var i = 0; i<$(currentTrackOrder).length; i++)
b@1608 499 {
b@1610 500 if (i!=index) // Make all other sliders green
b@1608 501 {
b@1608 502 document.getElementById('track-slider-'+i).style.backgroundColor = "rgb(100,200,100)";
b@1608 503 }
b@1608 504
b@1608 505 }
b@1609 506 audioEngineContext.play();
b@1610 507 hasBeenPlayed[index] = true; // mark as played
b@1608 508 };
b@1608 509
b@1608 510 canvas.appendChild(trackSliderObj);
b@1610 511
b@1610 512 // Add corresponding element to array to check whether sound has been played
b@1610 513 hasBeenPlayed.push(false);
b@1608 514 });
b@1608 515
b@1608 516 // Append any commentQuestion boxes
b@1608 517 var commentQuestions = $(textXML).find('CommentQuestion');
b@1608 518 $(commentQuestions).each(function(index,element) {
b@1608 519 // Create document objects to hold the comment boxes
b@1608 520 var trackComment = document.createElement('div');
b@1608 521 trackComment.className = 'comment-div commentQuestion';
b@1608 522 trackComment.id = element.attributes['id'].value;
b@1608 523 // Create a string next to each comment asking for a comment
b@1608 524 var trackString = document.createElement('span');
b@1608 525 trackString.innerHTML = element.textContent;
b@1608 526 // Create the HTML5 comment box 'textarea'
b@1608 527 var trackCommentBox = document.createElement('textarea');
b@1608 528 trackCommentBox.rows = '4';
b@1608 529 trackCommentBox.cols = '100';
b@1608 530 trackCommentBox.name = 'commentQuestion'+index;
b@1608 531 trackCommentBox.className = 'trackComment';
b@1608 532 var br = document.createElement('br');
b@1608 533 // Add to the holder.
b@1608 534 trackComment.appendChild(trackString);
b@1608 535 trackComment.appendChild(br);
b@1608 536 trackComment.appendChild(trackCommentBox);
b@1608 537 feedbackHolder.appendChild(trackComment);
b@1608 538 });
b@1608 539
b@1608 540 // Now process any pre-test commands
b@1608 541
b@1608 542 var preTest = $(testXMLSetups[id]).find('PreTest')[0];
b@1608 543 if (preTest.children.length > 0)
b@1608 544 {
b@1608 545 currentState = 'testRunPre-'+id;
b@1608 546 preTestPopupStart(preTest);
b@1608 547 showPopup();
b@1608 548 } else {
b@1608 549 currentState = 'testRun-'+id;
b@1608 550 }
b@1608 551 }
b@1608 552
b@1608 553 function preTestPopupStart(preTest)
b@1608 554 {
b@1608 555 var popupHolder = document.getElementById('popupHolder');
b@1608 556 popupHolder.innerHTML = null;
b@1608 557 // Parse the first box
b@1608 558 var preTestOption = document.createElement('div');
b@1608 559 preTestOption.id = 'preTest';
b@1608 560 preTestOption.style.marginTop = '25px';
b@1608 561 preTestOption.align = "center";
b@1608 562 var child = preTest.children[0];
b@1608 563 if (child.nodeName == 'statement')
b@1608 564 {
b@1608 565 preTestOption.innerHTML = '<span>'+child.innerHTML+'</span>';
b@1608 566 } else if (child.nodeName == 'question')
b@1608 567 {
b@1608 568 var questionId = child.attributes['id'].value;
b@1608 569 var textHold = document.createElement('span');
b@1608 570 textHold.innerHTML = child.innerHTML;
b@1608 571 textHold.id = questionId + 'response';
b@1608 572 var textEnter = document.createElement('textarea');
b@1608 573 preTestOption.appendChild(textHold);
b@1608 574 preTestOption.appendChild(textEnter);
b@1608 575 }
b@1608 576 var nextButton = document.createElement('button');
b@1608 577 nextButton.className = 'popupButton';
b@1608 578 nextButton.value = '0';
b@1608 579 nextButton.innerHTML = 'Next';
b@1608 580 nextButton.onclick = popupButtonClick;
b@1608 581
b@1608 582 popupHolder.appendChild(preTestOption);
b@1608 583 popupHolder.appendChild(nextButton);
b@1608 584 }
b@1608 585
b@1608 586 function popupButtonClick()
b@1608 587 {
b@1608 588 // Global call from the 'Next' button click
b@1608 589 if (currentState == 'preTest')
b@1608 590 {
b@1608 591 // At the start of the preTest routine!
b@1608 592 var xmlTree = projectXML.find('setup');
b@1608 593 var preTest = xmlTree.find('PreTest')[0];
b@1608 594 this.value = preTestButtonClick(preTest,this.value);
b@1608 595 } else if (currentState.substr(0,10) == 'testRunPre')
b@1608 596 {
b@1608 597 //Specific test pre-test
b@1608 598 var testId = currentState.substr(11,currentState.length-10);
b@1608 599 var preTest = $(testXMLSetups[testId]).find('PreTest')[0];
b@1608 600 this.value = preTestButtonClick(preTest,this.value);
b@1608 601 } else if (currentState.substr(0,11) == 'testRunPost')
b@1608 602 {
b@1608 603 // Specific test post-test
b@1608 604 var testId = currentState.substr(12,currentState.length-11);
b@1608 605 var preTest = $(testXMLSetups[testId]).find('PostTest')[0];
b@1608 606 this.value = preTestButtonClick(preTest,this.value);
b@1608 607 } else if (currentState == 'postTest')
b@1608 608 {
b@1608 609 // At the end of the test, running global post test
b@1608 610 var xmlTree = projectXML.find('setup');
b@1608 611 var PostTest = xmlTree.find('PostTest')[0];
b@1608 612 this.value = preTestButtonClick(PostTest,this.value);
b@1608 613 }
b@1608 614 }
b@1608 615
b@1608 616 function preTestButtonClick(preTest,index)
b@1608 617 {
b@1608 618 // Called on click of pre-test button
b@1608 619 // Need to find and parse preTest again!
b@1608 620 var preTestOption = document.getElementById('preTest');
b@1608 621 // Check if current state is a question!
b@1608 622 if (preTest.children[index].nodeName == 'question') {
b@1608 623 var questionId = preTest.children[index].attributes['id'].value;
b@1608 624 var questionHold = document.createElement('comment');
b@1608 625 var questionResponse = document.getElementById(questionId + 'response');
b@1608 626 var mandatory = preTest.children[index].attributes['mandatory'];
b@1608 627 if (mandatory != undefined){
b@1608 628 if (mandatory.value == 'true') {mandatory = true;}
b@1608 629 else {mandatory = false;}
b@1608 630 } else {mandatory = false;}
b@1608 631 if (mandatory == true && questionResponse.value.length == 0) {
b@1608 632 return index;
b@1608 633 }
b@1608 634 questionHold.id = questionId;
b@1608 635 questionHold.innerHTML = questionResponse.value;
b@1608 636 postPopupResponse(questionHold);
b@1608 637 }
b@1608 638 index++;
b@1608 639 if (index < preTest.children.length)
b@1608 640 {
b@1608 641 // More to process
b@1608 642 var child = preTest.children[index];
b@1608 643 if (child.nodeName == 'statement')
b@1608 644 {
b@1608 645 preTestOption.innerHTML = '<span>'+child.innerHTML+'</span>';
b@1608 646 } else if (child.nodeName == 'question')
b@1608 647 {
b@1608 648 var textHold = document.createElement('span');
b@1608 649 textHold.innerHTML = child.innerHTML;
b@1608 650 var textEnter = document.createElement('textarea');
b@1608 651 textEnter.id = child.attributes['id'].value + 'response';
b@1608 652 var br = document.createElement('br');
b@1608 653 preTestOption.innerHTML = null;
b@1608 654 preTestOption.appendChild(textHold);
b@1608 655 preTestOption.appendChild(br);
b@1608 656 preTestOption.appendChild(textEnter);
b@1608 657 }
b@1608 658 } else {
b@1608 659 // Time to clear
b@1608 660 preTestOption.innerHTML = null;
b@1608 661 if (currentState != 'postTest') {
b@1608 662 hidePopup();
b@1608 663 // Progress the state!
b@1608 664 advanceState();
b@1608 665 } else {
b@1608 666 a = createProjectSave(projectReturn);
b@1608 667 preTestOption.appendChild(a);
b@1608 668 }
b@1608 669 }
b@1608 670 return index;
b@1608 671 }
b@1608 672
b@1608 673 function postPopupResponse(response)
b@1608 674 {
b@1608 675 if (currentState == 'preTest') {
b@1608 676 preTestQuestions.appendChild(response);
b@1608 677 } else if (currentState == 'postTest') {
b@1608 678 postTestQuestions.appendChild(response);
b@1608 679 } else {
b@1608 680 // Inside a specific test
b@1608 681 if (currentState.substr(0,10) == 'testRunPre') {
b@1608 682 // Pre Test
b@1608 683 var store = $(currentTestHolder).find('preTest');
b@1608 684 } else {
b@1608 685 // Post Test
b@1608 686 var store = $(currentTestHolder).find('postTest');
b@1608 687 }
b@1608 688 store[0].appendChild(response);
b@1608 689 }
b@1608 690 }
b@1608 691
b@1608 692 function showPopup()
b@1608 693 {
b@1608 694 var popupHolder = document.getElementById('popupHolder');
b@1608 695 popupHolder.style.zIndex = 3;
b@1608 696 popupHolder.style.visibility = 'visible';
b@1608 697 var blank = document.getElementsByClassName('testHalt')[0];
b@1608 698 blank.style.zIndex = 2;
b@1608 699 blank.style.visibility = 'visible';
b@1608 700 }
b@1608 701
b@1608 702 function hidePopup()
b@1608 703 {
b@1608 704 var popupHolder = document.getElementById('popupHolder');
b@1608 705 popupHolder.style.zIndex = -1;
b@1608 706 popupHolder.style.visibility = 'hidden';
b@1608 707 var blank = document.getElementsByClassName('testHalt')[0];
b@1608 708 blank.style.zIndex = -2;
b@1608 709 blank.style.visibility = 'hidden';
b@1608 710 }
b@1608 711
b@1608 712 function dragEnd(ev) {
b@1608 713 // Function call when a div has been dropped
b@1608 714 var slider = document.getElementById('slider');
b@1608 715 var w = slider.style.width;
b@1608 716 w = Number(w.substr(0,w.length-2));
b@1608 717 var x = ev.x;
b@1608 718 if (x >= 42 && x < w+42) {
b@1608 719 this.style.left = (x)+'px';
b@1608 720 } else {
b@1608 721 if (x<42) {
b@1608 722 this.style.left = '42px';
b@1608 723 } else {
b@1608 724 this.style.left = (w+42) + 'px';
b@1608 725 }
b@1608 726 }
b@1608 727 audioEngineContext.metric.sliderMoved();
b@1608 728 }
b@1608 729
b@1608 730 function advanceState()
b@1608 731 {
b@1608 732 console.log(currentState);
b@1608 733 if (currentState == 'preTest')
b@1608 734 {
b@1608 735 // End of pre-test, begin the test
b@1608 736 loadTest(0);
b@1608 737 } else if (currentState.substr(0,10) == 'testRunPre')
b@1608 738 {
b@1608 739 // Start the test
b@1608 740 var testId = currentState.substr(11,currentState.length-10);
b@1608 741 currentState = 'testRun-'+testId;
b@1608 742 } else if (currentState.substr(0,11) == 'testRunPost')
b@1608 743 {
b@1608 744 var testId = currentState.substr(12,currentState.length-11);
b@1608 745 testEnded(testId);
b@1608 746 } else if (currentState.substr(0,7) == 'testRun')
b@1608 747 {
b@1608 748 var testId = currentState.substr(8,currentState.length-7);
b@1608 749 // Check if we have any post tests to perform
b@1608 750 var postXML = $(testXMLSetups[testId]).find('PostTest')[0];
b@1608 751 if (postXML == undefined) {
b@1608 752 testEnded(testId);
b@1608 753 }
b@1608 754 else if (postXML.children.length > 0)
b@1608 755 {
b@1608 756 currentState = 'testRunPost-'+testId;
b@1608 757 showPopup();
b@1608 758 preTestPopupStart(postXML);
b@1608 759 }
b@1608 760 else {
b@1608 761
b@1608 762
b@1608 763 // No post tests, check if we have another test to perform instead
b@1608 764
b@1608 765 }
b@1608 766 }
b@1608 767 console.log(currentState);
b@1608 768 }
b@1608 769
b@1608 770 function testEnded(testId)
b@1608 771 {
b@1608 772 pageXMLSave(testId);
b@1608 773 if (testXMLSetups.length-1 > testId)
b@1608 774 {
b@1608 775 // Yes we have another test to perform
b@1608 776 testId = (Number(testId)+1);
b@1608 777 currentState = 'testRun-'+testId;
b@1608 778 loadTest(testId);
b@1608 779 } else {
b@1608 780 console.log('Testing Completed!');
b@1608 781 currentState = 'postTest';
b@1608 782 // Check for any post tests
b@1608 783 var xmlSetup = projectXML.find('setup');
b@1608 784 var postTest = xmlSetup.find('PostTest')[0];
b@1608 785 showPopup();
b@1608 786 preTestPopupStart(postTest);
b@1608 787 }
b@1608 788 }
b@1608 789
b@1609 790 function buttonSubmitClick() // TODO: Only when all songs have been played!
b@1608 791 {
b@1610 792 if (hasBeenPlayed.indexOf(false)==-1)
b@1610 793 {
b@1610 794 if (audioEngineContext.status == 1) {
b@1610 795 var playback = document.getElementById('playback-button');
b@1610 796 playback.click();
b@1610 797 // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
b@1610 798 } else
b@1610 799 {
b@1610 800 if (audioEngineContext.timer.testStarted == false)
b@1610 801 {
b@1610 802 alert('You have not started the test! Please press start to begin the test!');
b@1610 803 return;
b@1610 804 }
b@1610 805 }
b@1610 806 if (currentState.substr(0,7) == 'testRun')
b@1610 807 {
b@1610 808 hasBeenPlayed = []; // clear array to prepare for next test
b@1610 809 audioEngineContext.timer.stopTest();
b@1610 810 advanceState();
b@1610 811 }
b@1610 812 } else // if a fragment has not been played yet
b@1610 813 {
b@1610 814 alert('You have not played fragment ' + hasBeenPlayed.indexOf(false) + ' yet. Please listen, rate and comment all samples before submitting.');
b@1610 815 return;
b@1610 816 }
b@1608 817 }
b@1608 818
b@1608 819 function convSliderPosToRate(id)
b@1608 820 {
b@1608 821 var w = document.getElementById('slider').style.width;
b@1608 822 var maxPix = w.substr(0,w.length-2);
b@1608 823 var slider = document.getElementsByClassName('track-slider')[id];
b@1608 824 var pix = slider.style.left;
b@1608 825 pix = pix.substr(0,pix.length-2);
b@1608 826 var rate = (pix-42)/maxPix;
b@1608 827 return rate;
b@1608 828 }
b@1608 829
b@1608 830 function pageXMLSave(testId)
b@1608 831 {
b@1608 832 // Saves a specific test page
b@1608 833 var xmlDoc = currentTestHolder;
b@1608 834 // Check if any session wide metrics are enabled
b@1608 835
b@1608 836 var commentShow = testXMLSetups[testId].attributes['elementComments'];
b@1608 837 if (commentShow != undefined) {
b@1608 838 if (commentShow.value == 'false') {commentShow = false;}
b@1608 839 else {commentShow = true;}
b@1608 840 } else {commentShow = true;}
b@1608 841
b@1608 842 var metric = document.createElement('metric');
b@1608 843 if (audioEngineContext.metric.enableTestTimer)
b@1608 844 {
b@1608 845 var testTime = document.createElement('metricResult');
b@1608 846 testTime.id = 'testTime';
b@1608 847 testTime.textContent = audioEngineContext.timer.testDuration;
b@1608 848 metric.appendChild(testTime);
b@1608 849 }
b@1608 850 xmlDoc.appendChild(metric);
b@1608 851 var trackSliderObjects = document.getElementsByClassName('track-slider');
b@1608 852 var commentObjects = document.getElementsByClassName('comment-div');
b@1608 853 for (var i=0; i<trackSliderObjects.length; i++)
b@1608 854 {
b@1608 855 var audioElement = document.createElement('audioElement');
b@1608 856 audioElement.id = currentTrackOrder[i].attributes['id'].value;
b@1608 857 audioElement.url = currentTrackOrder[i].attributes['url'].value;
b@1608 858 var value = document.createElement('value');
b@1608 859 value.innerHTML = convSliderPosToRate(i);
b@1608 860 if (commentShow) {
b@1608 861 var comment = document.createElement("comment");
b@1608 862 var question = document.createElement("question");
b@1608 863 var response = document.createElement("response");
b@1608 864 question.textContent = commentObjects[i].children[0].textContent;
b@1608 865 response.textContent = commentObjects[i].children[2].value;
b@1608 866 comment.appendChild(question);
b@1608 867 comment.appendChild(response);
b@1608 868 audioElement.appendChild(comment);
b@1608 869 }
b@1608 870 audioElement.appendChild(value);
b@1608 871 // Check for any per element metrics
b@1608 872 var metric = document.createElement('metric');
b@1608 873 var elementMetric = audioEngineContext.audioObjects[i].metric;
b@1608 874 if (audioEngineContext.metric.enableElementTimer) {
b@1608 875 var elementTimer = document.createElement('metricResult');
b@1608 876 elementTimer.id = 'elementTimer';
b@1608 877 elementTimer.textContent = elementMetric.listenedTimer;
b@1608 878 metric.appendChild(elementTimer);
b@1608 879 }
b@1608 880 if (audioEngineContext.metric.enableElementTracker) {
b@1608 881 var elementTrackerFull = document.createElement('metricResult');
b@1608 882 elementTrackerFull.id = 'elementTrackerFull';
b@1608 883 var data = elementMetric.movementTracker;
b@1608 884 for (var k=0; k<data.length; k++)
b@1608 885 {
b@1608 886 var timePos = document.createElement('timePos');
b@1608 887 timePos.id = k;
b@1608 888 var time = document.createElement('time');
b@1608 889 time.textContent = data[k][0];
b@1608 890 var position = document.createElement('position');
b@1608 891 position.textContent = data[k][1];
b@1608 892 timePos.appendChild(time);
b@1608 893 timePos.appendChild(position);
b@1608 894 elementTrackerFull.appendChild(timePos);
b@1608 895 }
b@1608 896 metric.appendChild(elementTrackerFull);
b@1608 897 }
b@1608 898 if (audioEngineContext.metric.enableElementInitialPosition) {
b@1608 899 var elementInitial = document.createElement('metricResult');
b@1608 900 elementInitial.id = 'elementInitialPosition';
b@1608 901 elementInitial.textContent = elementMetric.initialPosition;
b@1608 902 metric.appendChild(elementInitial);
b@1608 903 }
b@1608 904 if (audioEngineContext.metric.enableFlagListenedTo) {
b@1608 905 var flagListenedTo = document.createElement('metricResult');
b@1608 906 flagListenedTo.id = 'elementFlagListenedTo';
b@1608 907 flagListenedTo.textContent = elementMetric.wasListenedTo;
b@1608 908 metric.appendChild(flagListenedTo);
b@1608 909 }
b@1608 910 if (audioEngineContext.metric.enableFlagMoved) {
b@1608 911 var flagMoved = document.createElement('metricResult');
b@1608 912 flagMoved.id = 'elementFlagMoved';
b@1608 913 flagMoved.textContent = elementMetric.wasMoved;
b@1608 914 metric.appendChild(flagMoved);
b@1608 915 }
b@1608 916 if (audioEngineContext.metric.enableFlagComments) {
b@1608 917 var flagComments = document.createElement('metricResult');
b@1608 918 flagComments.id = 'elementFlagComments';
b@1608 919 if (response.textContent.length == 0) {flag.textContent = 'false';}
b@1608 920 else {flag.textContet = 'true';}
b@1608 921 metric.appendChild(flagComments);
b@1608 922 }
b@1608 923 audioElement.appendChild(metric);
b@1608 924 xmlDoc.appendChild(audioElement);
b@1608 925 }
b@1608 926 var commentQuestion = document.getElementsByClassName('commentQuestion');
b@1608 927 for (var i=0; i<commentQuestion.length; i++)
b@1608 928 {
b@1608 929 var cqHolder = document.createElement('CommentQuestion');
b@1608 930 var comment = document.createElement('comment');
b@1608 931 var question = document.createElement('question');
b@1608 932 cqHolder.id = commentQuestion[i].id;
b@1608 933 comment.textContent = commentQuestion[i].children[2].value;
b@1608 934 question.textContent = commentQuestion[i].children[0].textContent;
b@1608 935 cqHolder.appendChild(question);
b@1608 936 cqHolder.appendChild(comment);
b@1608 937 xmlDoc.appendChild(cqHolder);
b@1608 938 }
b@1608 939 testResultsHolders[testId] = xmlDoc;
b@1608 940 }
b@1608 941
b@1608 942 // Only other global function which must be defined in the interface class. Determines how to create the XML document.
b@1608 943 function interfaceXMLSave(){
b@1608 944 // Create the XML string to be exported with results
b@1608 945 var xmlDoc = document.createElement("BrowserEvaluationResult");
b@1608 946 for (var i=0; i<testResultsHolders.length; i++)
b@1608 947 {
b@1608 948 xmlDoc.appendChild(testResultsHolders[i]);
b@1608 949 }
b@1608 950 // Append Pre/Post Questions
b@1608 951 xmlDoc.appendChild(preTestQuestions);
b@1608 952 xmlDoc.appendChild(postTestQuestions);
b@1608 953
b@1608 954 return xmlDoc;
b@1608 955 }