annotate ape.js @ 113:dcb042ed9d76

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