annotate core.js @ 2150:5df4616b6e7f

Session now performs intermediate saves on each advanceState trigger (page completions, survey completions). Specification projectReturn now "local" for presentation saving
author Nicholas Jillings <nickjillings@users.noreply.github.com>
date Wed, 09 Mar 2016 12:41:26 +0000
parents 90212e9e9295
children 9cf727420c29
rev   line source
nickjillings@1682 1 /**
nickjillings@1682 2 * core.js
nickjillings@1682 3 *
nickjillings@1682 4 * Main script to run, calls all other core functions and manages loading/store to backend.
nickjillings@1682 5 * Also contains all global variables.
nickjillings@1682 6 */
nickjillings@1682 7
nickjillings@1682 8 /* create the web audio API context and store in audioContext*/
nickjillings@1643 9 var audioContext; // Hold the browser web audio API
nickjillings@1643 10 var projectXML; // Hold the parsed setup XML
nickjillings@1324 11 var schemaXSD; // Hold the parsed schema XSD
nickjillings@1581 12 var specification;
nickjillings@1582 13 var interfaceContext;
nickjillings@1324 14 var storage;
nickjillings@1622 15 var popup; // Hold the interfacePopup object
nickjillings@1634 16 var testState;
nickjillings@1655 17 var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order
nickjillings@1643 18 var audioEngineContext; // The custome AudioEngine object
nickjillings@1643 19 var projectReturn; // Hold the URL for the return
nickjillings@2013 20
nickjillings@1318 21
nickjillings@1318 22 // Add a prototype to the bufferSourceNode to reference to the audioObject holding it
nickjillings@1318 23 AudioBufferSourceNode.prototype.owner = undefined;
nickjillings@2101 24 // Add a prototype to the bufferSourceNode to hold when the object was given a play command
nickjillings@2101 25 AudioBufferSourceNode.prototype.playbackStartTime = undefined;
nickjillings@1318 26 // Add a prototype to the bufferNode to hold the desired LINEAR gain
nickjillings@1320 27 AudioBuffer.prototype.playbackGain = undefined;
nickjillings@1318 28 // Add a prototype to the bufferNode to hold the computed LUFS loudness
nickjillings@1318 29 AudioBuffer.prototype.lufs = undefined;
nickjillings@1318 30
nickjillings@1348 31 // Firefox does not have an XMLDocument.prototype.getElementsByName
nickjillings@1348 32 // and there is no searchAll style command, this custom function will
nickjillings@1348 33 // search all children recusrively for the name. Used for XSD where all
nickjillings@1348 34 // element nodes must have a name and therefore can pull the schema node
nickjillings@1348 35 XMLDocument.prototype.getAllElementsByName = function(name)
nickjillings@1348 36 {
nickjillings@1348 37 name = String(name);
nickjillings@1348 38 var selected = this.documentElement.getAllElementsByName(name);
nickjillings@1348 39 return selected;
nickjillings@1348 40 }
nickjillings@1348 41
nickjillings@1348 42 Element.prototype.getAllElementsByName = function(name)
nickjillings@1348 43 {
nickjillings@1348 44 name = String(name);
nickjillings@1348 45 var selected = [];
nickjillings@1348 46 var node = this.firstElementChild;
nickjillings@1348 47 while(node != null)
nickjillings@1348 48 {
nickjillings@1348 49 if (node.getAttribute('name') == name)
nickjillings@1348 50 {
nickjillings@1348 51 selected.push(node);
nickjillings@1348 52 }
nickjillings@1348 53 if (node.childElementCount > 0)
nickjillings@1348 54 {
nickjillings@1348 55 selected = selected.concat(node.getAllElementsByName(name));
nickjillings@1348 56 }
nickjillings@1348 57 node = node.nextElementSibling;
nickjillings@1348 58 }
nickjillings@1348 59 return selected;
nickjillings@1348 60 }
nickjillings@1348 61
nickjillings@1348 62 XMLDocument.prototype.getAllElementsByTagName = function(name)
nickjillings@1348 63 {
nickjillings@1348 64 name = String(name);
nickjillings@1348 65 var selected = this.documentElement.getAllElementsByTagName(name);
nickjillings@1348 66 return selected;
nickjillings@1348 67 }
nickjillings@1348 68
nickjillings@1348 69 Element.prototype.getAllElementsByTagName = function(name)
nickjillings@1348 70 {
nickjillings@1348 71 name = String(name);
nickjillings@1348 72 var selected = [];
nickjillings@1348 73 var node = this.firstElementChild;
nickjillings@1348 74 while(node != null)
nickjillings@1348 75 {
nickjillings@1348 76 if (node.nodeName == name)
nickjillings@1348 77 {
nickjillings@1348 78 selected.push(node);
nickjillings@1348 79 }
nickjillings@1348 80 if (node.childElementCount > 0)
nickjillings@1348 81 {
nickjillings@1348 82 selected = selected.concat(node.getAllElementsByTagName(name));
nickjillings@1348 83 }
nickjillings@1348 84 node = node.nextElementSibling;
nickjillings@1348 85 }
nickjillings@1348 86 return selected;
nickjillings@1348 87 }
nickjillings@1348 88
nickjillings@1348 89 // Firefox does not have an XMLDocument.prototype.getElementsByName
nickjillings@1348 90 if (typeof XMLDocument.prototype.getElementsByName != "function") {
nickjillings@1348 91 XMLDocument.prototype.getElementsByName = function(name)
nickjillings@1348 92 {
nickjillings@1348 93 name = String(name);
nickjillings@1348 94 var node = this.documentElement.firstElementChild;
nickjillings@1348 95 var selected = [];
nickjillings@1348 96 while(node != null)
nickjillings@1348 97 {
nickjillings@1348 98 if (node.getAttribute('name') == name)
nickjillings@1348 99 {
nickjillings@1348 100 selected.push(node);
nickjillings@1348 101 }
nickjillings@1348 102 node = node.nextElementSibling;
nickjillings@1348 103 }
nickjillings@1348 104 return selected;
nickjillings@1348 105 }
nickjillings@1348 106 }
nickjillings@1348 107
nickjillings@1682 108 window.onload = function() {
nickjillings@1682 109 // Function called once the browser has loaded all files.
nickjillings@1682 110 // This should perform any initial commands such as structure / loading documents
nickjillings@1682 111
nickjillings@1682 112 // Create a web audio API context
nickjillings@1701 113 // Fixed for cross-browser support
nickjillings@1701 114 var AudioContext = window.AudioContext || window.webkitAudioContext;
nickjillings@1688 115 audioContext = new AudioContext;
nickjillings@1682 116
nickjillings@1634 117 // Create test state
nickjillings@1634 118 testState = new stateMachine();
nickjillings@1634 119
nickjillings@1622 120 // Create the popup interface object
nickjillings@1622 121 popup = new interfacePopup();
nickjillings@1370 122
nickjillings@1370 123 // Create the specification object
nickjillings@1581 124 specification = new Specification();
nickjillings@1582 125
nickjillings@1582 126 // Create the interface object
nickjillings@1582 127 interfaceContext = new Interface(specification);
nickjillings@1324 128
nickjillings@1324 129 // Create the storage object
nickjillings@1324 130 storage = new Storage();
nickjillings@1410 131 // Define window callbacks for interface
nickjillings@1410 132 window.onresize = function(event){interfaceContext.resizeWindow(event);};
nickjillings@1697 133 };
nickjillings@1682 134
nickjillings@1408 135 function loadProjectSpec(url) {
nickjillings@1408 136 // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data
nickjillings@1408 137 // If url is null, request client to upload project XML document
nickjillings@1324 138 var xmlhttp = new XMLHttpRequest();
nickjillings@1324 139 xmlhttp.open("GET",'test-schema.xsd',true);
nickjillings@1324 140 xmlhttp.onload = function()
nickjillings@1324 141 {
nickjillings@1324 142 schemaXSD = xmlhttp.response;
nickjillings@1324 143 var parse = new DOMParser();
nickjillings@1324 144 specification.schema = parse.parseFromString(xmlhttp.response,'text/xml');
nickjillings@1324 145 var r = new XMLHttpRequest();
nickjillings@1324 146 r.open('GET',url,true);
nickjillings@1324 147 r.onload = function() {
nickjillings@1324 148 loadProjectSpecCallback(r.response);
nickjillings@1324 149 };
nickjillings@2110 150 r.onerror = function() {
nickjillings@2110 151 document.getElementsByTagName('body')[0].innerHTML = null;
nickjillings@2110 152 var msg = document.createElement("h3");
nickjillings@2110 153 msg.textContent = "FATAL ERROR";
nickjillings@2110 154 var span = document.createElement("p");
nickjillings@2110 155 span.textContent = "There was an error when loading your XML file. Please check your path in the URL. After the path to this page, there should be '?url=path/to/your/file.xml'. Check the spelling of your filename as well. If you are still having issues, check the log of the python server or your webserver distribution for 404 codes for your file.";
nickjillings@2110 156 document.getElementsByTagName('body')[0].appendChild(msg);
nickjillings@2110 157 document.getElementsByTagName('body')[0].appendChild(span);
nickjillings@2110 158 }
nickjillings@1324 159 r.send();
nickjillings@1408 160 };
nickjillings@1324 161 xmlhttp.send();
nickjillings@1408 162 };
nickjillings@1408 163
nickjillings@1408 164 function loadProjectSpecCallback(response) {
nickjillings@1408 165 // Function called after asynchronous download of XML project specification
nickjillings@1408 166 //var decode = $.parseXML(response);
nickjillings@1408 167 //projectXML = $(decode);
nickjillings@1408 168
nickjillings@2147 169 // Check if XML is new or a resumption
nickjillings@2147 170 var parse = new DOMParser();
nickjillings@2147 171 var responseDocument = parse.parseFromString(response,'text/xml');
nickjillings@2147 172 var errorNode = responseDocument.getElementsByTagName('parsererror');
nickjillings@1443 173 if (errorNode.length >= 1)
nickjillings@1443 174 {
nickjillings@1443 175 var msg = document.createElement("h3");
nickjillings@1443 176 msg.textContent = "FATAL ERROR";
nickjillings@1443 177 var span = document.createElement("span");
nickjillings@1443 178 span.textContent = "The XML parser returned the following errors when decoding your XML file";
nickjillings@1445 179 document.getElementsByTagName('body')[0].innerHTML = null;
nickjillings@1443 180 document.getElementsByTagName('body')[0].appendChild(msg);
nickjillings@1443 181 document.getElementsByTagName('body')[0].appendChild(span);
nickjillings@1443 182 document.getElementsByTagName('body')[0].appendChild(errorNode[0]);
nickjillings@1443 183 return;
nickjillings@1443 184 }
nickjillings@2147 185 if (responseDocument.children[0].nodeName == "waet") {
nickjillings@2147 186 // document is a specification
nickjillings@2147 187
nickjillings@2147 188 // Perform XML schema validation
nickjillings@2147 189 var Module = {
nickjillings@2147 190 xml: response,
nickjillings@2147 191 schema: schemaXSD,
nickjillings@2147 192 arguments:["--noout", "--schema", 'test-schema.xsd','document.xml']
nickjillings@2147 193 };
nickjillings@2147 194 projectXML = responseDocument;
nickjillings@2147 195 var xmllint = validateXML(Module);
nickjillings@2147 196 console.log(xmllint);
nickjillings@2147 197 if(xmllint != 'document.xml validates\n')
nickjillings@2147 198 {
nickjillings@2147 199 document.getElementsByTagName('body')[0].innerHTML = null;
nickjillings@2147 200 var msg = document.createElement("h3");
nickjillings@2147 201 msg.textContent = "FATAL ERROR";
nickjillings@2147 202 var span = document.createElement("h4");
nickjillings@2147 203 span.textContent = "The XML validator returned the following errors when decoding your XML file";
nickjillings@2147 204 document.getElementsByTagName('body')[0].appendChild(msg);
nickjillings@2147 205 document.getElementsByTagName('body')[0].appendChild(span);
nickjillings@2147 206 xmllint = xmllint.split('\n');
nickjillings@2147 207 for (var i in xmllint)
nickjillings@2147 208 {
nickjillings@2147 209 document.getElementsByTagName('body')[0].appendChild(document.createElement('br'));
nickjillings@2147 210 var span = document.createElement("span");
nickjillings@2147 211 span.textContent = xmllint[i];
nickjillings@2147 212 document.getElementsByTagName('body')[0].appendChild(span);
nickjillings@2147 213 }
nickjillings@2147 214 return;
nickjillings@2147 215 }
nickjillings@2149 216 // Build the specification
nickjillings@2149 217 specification.decode(projectXML);
nickjillings@2147 218 // Generate the session-key
nickjillings@2147 219 storage.initialise();
nickjillings@2147 220
nickjillings@2147 221 } else if (responseDocument.children[0].nodeName == "waetresult") {
nickjillings@2147 222 // document is a result
nickjillings@2147 223 projectXML = responseDocument.getElementsByTagName('waet')[0];
nickjillings@2149 224 // Build the specification
nickjillings@2149 225 specification.decode(projectXML);
nickjillings@2147 226 // Use the session-key
nickjillings@2147 227 var sessionKey = responseDocument.children[0].getAttribute(key);
nickjillings@2147 228 storage.initialise(sessionKey);
nickjillings@2147 229 }
nickjillings@1339 230 /// CHECK FOR SAMPLE RATE COMPATIBILITY
nickjillings@1339 231 if (specification.sampleRate != undefined) {
nickjillings@1339 232 if (Number(specification.sampleRate) != audioContext.sampleRate) {
nickjillings@1339 233 var errStr = 'Sample rates do not match! Requested '+Number(specification.sampleRate)+', got '+audioContext.sampleRate+'. Please set the sample rate to match before completing this test.';
nickjillings@1339 234 alert(errStr);
nickjillings@1339 235 return;
nickjillings@1339 236 }
nickjillings@1339 237 }
nickjillings@1408 238
nickjillings@1408 239 // Detect the interface to use and load the relevant javascripts.
nickjillings@1408 240 var interfaceJS = document.createElement('script');
nickjillings@1408 241 interfaceJS.setAttribute("type","text/javascript");
nickjillings@1329 242 switch(specification.interface)
nickjillings@1329 243 {
nickjillings@1329 244 case "APE":
nickjillings@1341 245 interfaceJS.setAttribute("src","interfaces/ape.js");
nickjillings@1408 246
nickjillings@1408 247 // APE comes with a css file
nickjillings@1408 248 var css = document.createElement('link');
nickjillings@1408 249 css.rel = 'stylesheet';
nickjillings@1408 250 css.type = 'text/css';
nickjillings@1341 251 css.href = 'interfaces/ape.css';
nickjillings@1408 252
nickjillings@1408 253 document.getElementsByTagName("head")[0].appendChild(css);
nickjillings@1329 254 break;
nickjillings@1329 255
nickjillings@1329 256 case "MUSHRA":
nickjillings@1341 257 interfaceJS.setAttribute("src","interfaces/mushra.js");
nickjillings@1408 258
nickjillings@1408 259 // MUSHRA comes with a css file
nickjillings@1408 260 var css = document.createElement('link');
nickjillings@1408 261 css.rel = 'stylesheet';
nickjillings@1408 262 css.type = 'text/css';
nickjillings@1341 263 css.href = 'interfaces/mushra.css';
nickjillings@1408 264
nickjillings@1408 265 document.getElementsByTagName("head")[0].appendChild(css);
nickjillings@1329 266 break;
nickjillings@1329 267
nickjillings@1329 268 case "AB":
nickjillings@1341 269 interfaceJS.setAttribute("src","interfaces/AB.js");
nickjillings@1329 270
nickjillings@1329 271 // AB comes with a css file
nickjillings@1329 272 var css = document.createElement('link');
nickjillings@1329 273 css.rel = 'stylesheet';
nickjillings@1329 274 css.type = 'text/css';
nickjillings@1341 275 css.href = 'interfaces/AB.css';
nickjillings@1329 276
nickjillings@1329 277 document.getElementsByTagName("head")[0].appendChild(css);
nickjillings@1345 278 break;
nickjillings@1345 279 case "Bipolar":
nickjillings@1345 280 case "ACR":
nickjillings@1345 281 case "DCR":
nickjillings@1345 282 case "CCR":
nickjillings@1343 283 case "ABC":
nickjillings@1343 284 // Above enumerate to horizontal sliders
nickjillings@1343 285 interfaceJS.setAttribute("src","interfaces/horizontal-sliders.js");
nickjillings@1343 286
nickjillings@1343 287 // horizontal-sliders comes with a css file
nickjillings@1343 288 var css = document.createElement('link');
nickjillings@1343 289 css.rel = 'stylesheet';
nickjillings@1343 290 css.type = 'text/css';
nickjillings@1343 291 css.href = 'interfaces/horizontal-sliders.css';
nickjillings@1343 292
nickjillings@1343 293 document.getElementsByTagName("head")[0].appendChild(css);
nickjillings@1345 294 break;
nickjillings@1345 295 case "discrete":
nickjillings@1345 296 case "likert":
nickjillings@1345 297 // Above enumerate to horizontal discrete radios
nickjillings@1345 298 interfaceJS.setAttribute("src","interfaces/discrete.js");
nickjillings@1345 299
nickjillings@1345 300 // horizontal-sliders comes with a css file
nickjillings@1345 301 var css = document.createElement('link');
nickjillings@1345 302 css.rel = 'stylesheet';
nickjillings@1345 303 css.type = 'text/css';
nickjillings@1345 304 css.href = 'interfaces/discrete.css';
nickjillings@1345 305
nickjillings@1345 306 document.getElementsByTagName("head")[0].appendChild(css);
nickjillings@1345 307 break;
nickjillings@1408 308 }
nickjillings@1408 309 document.getElementsByTagName("head")[0].appendChild(interfaceJS);
nickjillings@1408 310
nickjillings@1410 311 // Create the audio engine object
nickjillings@1410 312 audioEngineContext = new AudioEngine(specification);
nickjillings@1410 313
nickjillings@1324 314 $(specification.pages).each(function(index,elem){
nickjillings@1410 315 $(elem.audioElements).each(function(i,audioElem){
nickjillings@1324 316 var URL = elem.hostURL + audioElem.url;
nickjillings@1410 317 var buffer = null;
nickjillings@1410 318 for (var i=0; i<audioEngineContext.buffers.length; i++)
nickjillings@1410 319 {
nickjillings@1410 320 if (URL == audioEngineContext.buffers[i].url)
nickjillings@1410 321 {
nickjillings@1410 322 buffer = audioEngineContext.buffers[i];
nickjillings@1410 323 break;
nickjillings@1410 324 }
nickjillings@1410 325 }
nickjillings@1410 326 if (buffer == null)
nickjillings@1410 327 {
nickjillings@1430 328 buffer = new audioEngineContext.bufferObj();
nickjillings@1430 329 buffer.getMedia(URL);
nickjillings@1410 330 audioEngineContext.buffers.push(buffer);
nickjillings@1410 331 }
nickjillings@1410 332 });
nickjillings@1410 333 });
nickjillings@1408 334 }
nickjillings@1408 335
nickjillings@1408 336 function createProjectSave(destURL) {
nickjillings@1408 337 // Save the data from interface into XML and send to destURL
nickjillings@1408 338 // If destURL is null then download XML in client
nickjillings@1408 339 // Now time to render file locally
nickjillings@1408 340 var xmlDoc = interfaceXMLSave();
nickjillings@1408 341 var parent = document.createElement("div");
nickjillings@1408 342 parent.appendChild(xmlDoc);
nickjillings@1408 343 var file = [parent.innerHTML];
nickjillings@2150 344 if (destURL == "local") {
nickjillings@1408 345 var bb = new Blob(file,{type : 'application/xml'});
nickjillings@1408 346 var dnlk = window.URL.createObjectURL(bb);
nickjillings@1408 347 var a = document.createElement("a");
nickjillings@1408 348 a.hidden = '';
nickjillings@1408 349 a.href = dnlk;
nickjillings@1408 350 a.download = "save.xml";
nickjillings@1408 351 a.textContent = "Save File";
nickjillings@1408 352
nickjillings@1408 353 popup.showPopup();
nickjillings@1332 354 popup.popupContent.innerHTML = "</span>Please save the file below to give to your test supervisor</span><br>";
nickjillings@1408 355 popup.popupContent.appendChild(a);
nickjillings@1408 356 } else {
nickjillings@1408 357 var xmlhttp = new XMLHttpRequest;
nickjillings@1408 358 xmlhttp.open("POST",destURL,true);
nickjillings@1408 359 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
nickjillings@1408 360 xmlhttp.onerror = function(){
nickjillings@1408 361 console.log('Error saving file to server! Presenting download locally');
nickjillings@1408 362 createProjectSave(null);
nickjillings@1408 363 };
nickjillings@2150 364 xmlhttp.onload = function() {
nickjillings@2150 365 console.log(xmlhttp);
nickjillings@2150 366 if (this.status >= 300) {
nickjillings@2150 367 console.log("WARNING - Could not update at this time");
nickjillings@2150 368 } else {
nickjillings@2150 369 var parser = new DOMParser();
nickjillings@2150 370 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
nickjillings@2150 371 var response = xmlDoc.getElementsByTagName('response')[0];
nickjillings@2150 372 if (response.getAttribute("state") == "OK") {
nickjillings@2150 373 var file = response.getElementsByTagName("file")[0];
nickjillings@2150 374 console.log("Save: OK, written "+file.getAttribute("bytes")+"B");
nickjillings@2150 375 } else {
nickjillings@2150 376 var message = response.getElementsByTagName("message");
nickjillings@2150 377 console.log("Save: Error! "+message.textContent);
nickjillings@2150 378 createProjectSave("local");
nickjillings@2150 379 }
nickjillings@2150 380 }
nickjillings@2150 381 };
nickjillings@1408 382 xmlhttp.send(file);
nickjillings@1332 383 popup.showPopup();
nickjillings@1332 384 popup.popupContent.innerHTML = null;
nickjillings@1332 385 popup.popupContent.textContent = "Submitting. Please Wait";
nickjillings@2106 386 popup.hideNextButton();
nickjillings@2106 387 popup.hidePreviousButton();
nickjillings@1408 388 }
nickjillings@1408 389 }
nickjillings@1408 390
nickjillings@1408 391 function errorSessionDump(msg){
nickjillings@1408 392 // Create the partial interface XML save
nickjillings@1408 393 // Include error node with message on why the dump occured
nickjillings@1443 394 popup.showPopup();
nickjillings@1443 395 popup.popupContent.innerHTML = null;
nickjillings@1443 396 var err = document.createElement('error');
nickjillings@1443 397 var parent = document.createElement("div");
nickjillings@1443 398 if (typeof msg === "object")
nickjillings@1443 399 {
nickjillings@1443 400 err.appendChild(msg);
nickjillings@1443 401 popup.popupContent.appendChild(msg);
nickjillings@1443 402
nickjillings@1443 403 } else {
nickjillings@1443 404 err.textContent = msg;
nickjillings@1443 405 popup.popupContent.innerHTML = "ERROR : "+msg;
nickjillings@1443 406 }
nickjillings@1408 407 var xmlDoc = interfaceXMLSave();
nickjillings@1408 408 xmlDoc.appendChild(err);
nickjillings@1408 409 parent.appendChild(xmlDoc);
nickjillings@1408 410 var file = [parent.innerHTML];
nickjillings@1408 411 var bb = new Blob(file,{type : 'application/xml'});
nickjillings@1408 412 var dnlk = window.URL.createObjectURL(bb);
nickjillings@1408 413 var a = document.createElement("a");
nickjillings@1408 414 a.hidden = '';
nickjillings@1408 415 a.href = dnlk;
nickjillings@1408 416 a.download = "save.xml";
nickjillings@1408 417 a.textContent = "Save File";
nickjillings@1408 418
nickjillings@1443 419
nickjillings@1443 420
nickjillings@1408 421 popup.popupContent.appendChild(a);
nickjillings@1408 422 }
nickjillings@1408 423
nickjillings@1408 424 // Only other global function which must be defined in the interface class. Determines how to create the XML document.
nickjillings@1408 425 function interfaceXMLSave(){
nickjillings@1408 426 // Create the XML string to be exported with results
nickjillings@1324 427 return storage.finish();
nickjillings@1408 428 }
nickjillings@1408 429
nickjillings@1426 430 function linearToDecibel(gain)
nickjillings@1426 431 {
nickjillings@1426 432 return 20.0*Math.log10(gain);
nickjillings@1426 433 }
nickjillings@1426 434
nickjillings@1426 435 function decibelToLinear(gain)
nickjillings@1426 436 {
nickjillings@1426 437 return Math.pow(10,gain/20.0);
nickjillings@1426 438 }
nickjillings@1426 439
nickjillings@2147 440 function randomString(length) {
nickjillings@2147 441 return Math.round((Math.pow(36, length + 1) - Math.random() * Math.pow(36, length))).toString(36).slice(1);
nickjillings@2147 442 }
nickjillings@2147 443
nickjillings@1622 444 function interfacePopup() {
nickjillings@1622 445 // Creates an object to manage the popup
nickjillings@1622 446 this.popup = null;
nickjillings@1622 447 this.popupContent = null;
nickjillings@1526 448 this.popupTitle = null;
nickjillings@1526 449 this.popupResponse = null;
nickjillings@1574 450 this.buttonProceed = null;
nickjillings@2034 451 this.buttonPrevious = null;
nickjillings@1622 452 this.popupOptions = null;
nickjillings@1622 453 this.currentIndex = null;
nickjillings@1324 454 this.node = null;
nickjillings@1324 455 this.store = null;
nickjillings@1422 456 $(window).keypress(function(e){
nickjillings@1422 457 if (e.keyCode == 13 && popup.popup.style.visibility == 'visible')
nickjillings@1422 458 {
nickjillings@1422 459 console.log(e);
nickjillings@1422 460 popup.buttonProceed.onclick();
nickjillings@1424 461 e.preventDefault();
nickjillings@1422 462 }
nickjillings@1422 463 });
nickjillings@1581 464
nickjillings@1622 465 this.createPopup = function(){
nickjillings@1622 466 // Create popup window interface
nickjillings@1622 467 var insertPoint = document.getElementById("topLevelBody");
nickjillings@1622 468
nickjillings@1379 469 this.popup = document.getElementById('popupHolder');
nickjillings@1622 470 this.popup.style.left = (window.innerWidth/2)-250 + 'px';
nickjillings@1622 471 this.popup.style.top = (window.innerHeight/2)-125 + 'px';
nickjillings@1622 472
nickjillings@1379 473 this.popupContent = document.getElementById('popupContent');
nickjillings@1622 474
nickjillings@1379 475 this.popupTitle = document.getElementById('popupTitle');
nickjillings@1526 476
nickjillings@1379 477 this.popupResponse = document.getElementById('popupResponse');
nickjillings@1526 478
nickjillings@1379 479 this.buttonProceed = document.getElementById('popup-proceed');
nickjillings@1574 480 this.buttonProceed.onclick = function(){popup.proceedClicked();};
nickjillings@2034 481
nickjillings@1379 482 this.buttonPrevious = document.getElementById('popup-previous');
nickjillings@2034 483 this.buttonPrevious.onclick = function(){popup.previousClick();};
nickjillings@2034 484
nickjillings@1379 485 this.hidePopup();
nickjillings@1379 486
nickjillings@1581 487 this.popup.style.zIndex = -1;
nickjillings@1581 488 this.popup.style.visibility = 'hidden';
nickjillings@1622 489 };
nickjillings@1621 490
nickjillings@1622 491 this.showPopup = function(){
nickjillings@1581 492 if (this.popup == null) {
nickjillings@1622 493 this.createPopup();
nickjillings@1622 494 }
nickjillings@1622 495 this.popup.style.zIndex = 3;
nickjillings@1622 496 this.popup.style.visibility = 'visible';
nickjillings@1622 497 var blank = document.getElementsByClassName('testHalt')[0];
nickjillings@1622 498 blank.style.zIndex = 2;
nickjillings@1622 499 blank.style.visibility = 'visible';
nickjillings@1622 500 };
nickjillings@1622 501
nickjillings@1622 502 this.hidePopup = function(){
nickjillings@1622 503 this.popup.style.zIndex = -1;
nickjillings@1622 504 this.popup.style.visibility = 'hidden';
nickjillings@1622 505 var blank = document.getElementsByClassName('testHalt')[0];
nickjillings@1622 506 blank.style.zIndex = -2;
nickjillings@1622 507 blank.style.visibility = 'hidden';
nickjillings@1526 508 this.buttonPrevious.style.visibility = 'inherit';
nickjillings@1622 509 };
nickjillings@1622 510
nickjillings@1622 511 this.postNode = function() {
nickjillings@1622 512 // This will take the node from the popupOptions and display it
nickjillings@1622 513 var node = this.popupOptions[this.currentIndex];
nickjillings@1526 514 this.popupResponse.innerHTML = null;
nickjillings@1324 515 this.popupTitle.textContent = node.specification.statement;
nickjillings@1324 516 if (node.specification.type == 'question') {
nickjillings@1622 517 var textArea = document.createElement('textarea');
nickjillings@1324 518 switch (node.specification.boxsize) {
nickjillings@2030 519 case 'small':
nickjillings@2030 520 textArea.cols = "20";
nickjillings@2030 521 textArea.rows = "1";
nickjillings@2030 522 break;
nickjillings@2030 523 case 'normal':
nickjillings@2030 524 textArea.cols = "30";
nickjillings@2030 525 textArea.rows = "2";
nickjillings@2030 526 break;
nickjillings@2030 527 case 'large':
nickjillings@2030 528 textArea.cols = "40";
nickjillings@2030 529 textArea.rows = "5";
nickjillings@2030 530 break;
nickjillings@2030 531 case 'huge':
nickjillings@2030 532 textArea.cols = "50";
nickjillings@2030 533 textArea.rows = "10";
nickjillings@2030 534 break;
nickjillings@2030 535 }
nickjillings@1361 536 if (node.response == undefined) {
nickjillings@1361 537 node.response = "";
nickjillings@1361 538 } else {
nickjillings@1361 539 textArea.value = node.response;
nickjillings@1361 540 }
nickjillings@1526 541 this.popupResponse.appendChild(textArea);
nickjillings@1526 542 textArea.focus();
nickjillings@1382 543 this.popupResponse.style.textAlign="center";
nickjillings@1382 544 this.popupResponse.style.left="0%";
nickjillings@1324 545 } else if (node.specification.type == 'checkbox') {
nickjillings@1361 546 if (node.response == undefined) {
nickjillings@1361 547 node.response = Array(node.specification.options.length);
nickjillings@1361 548 }
nickjillings@1361 549 var index = 0;
nickjillings@1382 550 var max_w = 0;
nickjillings@1324 551 for (var option of node.specification.options) {
nickjillings@1588 552 var input = document.createElement('input');
nickjillings@2093 553 input.id = option.name;
nickjillings@1588 554 input.type = 'checkbox';
nickjillings@1588 555 var span = document.createElement('span');
nickjillings@1588 556 span.textContent = option.text;
nickjillings@1588 557 var hold = document.createElement('div');
nickjillings@1588 558 hold.setAttribute('name','option');
nickjillings@1588 559 hold.style.padding = '4px';
nickjillings@1588 560 hold.appendChild(input);
nickjillings@1588 561 hold.appendChild(span);
nickjillings@1324 562 this.popupResponse.appendChild(hold);
nickjillings@1361 563 if (node.response[index] != undefined){
nickjillings@1361 564 if (node.response[index].checked == true) {
nickjillings@1361 565 input.checked = "true";
nickjillings@1361 566 }
nickjillings@1361 567 }
nickjillings@1382 568 var w = $(span).width();
nickjillings@1382 569 if (w > max_w)
nickjillings@1382 570 max_w = w;
nickjillings@1361 571 index++;
nickjillings@1588 572 }
nickjillings@1382 573 max_w += 12;
nickjillings@1382 574 this.popupResponse.style.textAlign="";
nickjillings@1382 575 var leftP = ((max_w/500)/2)*100;
nickjillings@1382 576 this.popupResponse.style.left=leftP+"%";
nickjillings@1324 577 } else if (node.specification.type == 'radio') {
nickjillings@1361 578 if (node.response == undefined) {
nickjillings@1361 579 node.response = {name: "", text: ""};
nickjillings@1361 580 }
nickjillings@1361 581 var index = 0;
nickjillings@1382 582 var max_w = 0;
nickjillings@1324 583 for (var option of node.specification.options) {
nickjillings@1589 584 var input = document.createElement('input');
nickjillings@1589 585 input.id = option.name;
nickjillings@1589 586 input.type = 'radio';
nickjillings@1324 587 input.name = node.specification.id;
nickjillings@1589 588 var span = document.createElement('span');
nickjillings@1589 589 span.textContent = option.text;
nickjillings@1589 590 var hold = document.createElement('div');
nickjillings@1589 591 hold.setAttribute('name','option');
nickjillings@1589 592 hold.style.padding = '4px';
nickjillings@1589 593 hold.appendChild(input);
nickjillings@1589 594 hold.appendChild(span);
nickjillings@1324 595 this.popupResponse.appendChild(hold);
nickjillings@1361 596 if (input.id == node.response.name) {
nickjillings@1361 597 input.checked = "true";
nickjillings@1361 598 }
nickjillings@1382 599 var w = $(span).width();
nickjillings@1382 600 if (w > max_w)
nickjillings@1382 601 max_w = w;
nickjillings@1589 602 }
nickjillings@1382 603 max_w += 12;
nickjillings@1382 604 this.popupResponse.style.textAlign="";
nickjillings@1382 605 var leftP = ((max_w/500)/2)*100;
nickjillings@1382 606 this.popupResponse.style.left=leftP+"%";
nickjillings@1324 607 } else if (node.specification.type == 'number') {
nickjillings@1573 608 var input = document.createElement('input');
nickjillings@2044 609 input.type = 'textarea';
nickjillings@1324 610 if (node.min != null) {input.min = node.specification.min;}
nickjillings@1324 611 if (node.max != null) {input.max = node.specification.max;}
nickjillings@1324 612 if (node.step != null) {input.step = node.specification.step;}
nickjillings@1361 613 if (node.response != undefined) {
nickjillings@1361 614 input.value = node.response;
nickjillings@1361 615 }
nickjillings@1526 616 this.popupResponse.appendChild(input);
nickjillings@1382 617 this.popupResponse.style.textAlign="center";
nickjillings@1382 618 this.popupResponse.style.left="0%";
nickjillings@1622 619 }
nickjillings@2034 620 if(this.currentIndex+1 == this.popupOptions.length) {
nickjillings@1324 621 if (this.node.location == "pre") {
nickjillings@1531 622 this.buttonProceed.textContent = 'Start';
nickjillings@1531 623 } else {
nickjillings@1531 624 this.buttonProceed.textContent = 'Submit';
nickjillings@1531 625 }
nickjillings@2034 626 } else {
nickjillings@2034 627 this.buttonProceed.textContent = 'Next';
nickjillings@2034 628 }
nickjillings@2034 629 if(this.currentIndex > 0)
nickjillings@1526 630 this.buttonPrevious.style.visibility = 'visible';
nickjillings@1526 631 else
nickjillings@1526 632 this.buttonPrevious.style.visibility = 'hidden';
nickjillings@2015 633 };
nickjillings@1622 634
nickjillings@1324 635 this.initState = function(node,store) {
nickjillings@1622 636 //Call this with your preTest and postTest nodes when needed to
nickjillings@1622 637 // initialise the popup procedure.
nickjillings@1324 638 if (node.options.length > 0) {
nickjillings@1324 639 this.popupOptions = [];
nickjillings@1324 640 this.node = node;
nickjillings@1324 641 this.store = store;
nickjillings@1324 642 for (var opt of node.options)
nickjillings@1324 643 {
nickjillings@1324 644 this.popupOptions.push({
nickjillings@1324 645 specification: opt,
nickjillings@1324 646 response: null
nickjillings@1324 647 });
nickjillings@1324 648 }
nickjillings@1622 649 this.currentIndex = 0;
nickjillings@1622 650 this.showPopup();
nickjillings@1622 651 this.postNode();
nickjillings@1581 652 } else {
nickjillings@1581 653 advanceState();
nickjillings@1622 654 }
nickjillings@2015 655 };
nickjillings@1622 656
nickjillings@1574 657 this.proceedClicked = function() {
nickjillings@1622 658 // Each time the popup button is clicked!
nickjillings@1622 659 var node = this.popupOptions[this.currentIndex];
nickjillings@1324 660 if (node.specification.type == 'question') {
nickjillings@1622 661 // Must extract the question data
nickjillings@1622 662 var textArea = $(popup.popupContent).find('textarea')[0];
nickjillings@1324 663 if (node.specification.mandatory == true && textArea.value.length == 0) {
nickjillings@1622 664 alert('This question is mandatory');
nickjillings@1622 665 return;
nickjillings@1622 666 } else {
nickjillings@1622 667 // Save the text content
nickjillings@1324 668 console.log("Question: "+ node.specification.statement);
nickjillings@1623 669 console.log("Question Response: "+ textArea.value);
nickjillings@1324 670 node.response = textArea.value;
nickjillings@1622 671 }
nickjillings@1324 672 } else if (node.specification.type == 'checkbox') {
nickjillings@1588 673 // Must extract checkbox data
nickjillings@1326 674 console.log("Checkbox: "+ node.specification.statement);
nickjillings@1324 675 var inputs = this.popupResponse.getElementsByTagName('input');
nickjillings@1324 676 node.response = [];
nickjillings@1324 677 for (var i=0; i<node.specification.options.length; i++) {
nickjillings@1324 678 node.response.push({
nickjillings@1324 679 name: node.specification.options[i].name,
nickjillings@1324 680 text: node.specification.options[i].text,
nickjillings@1324 681 checked: inputs[i].checked
nickjillings@1324 682 });
nickjillings@1326 683 console.log(node.specification.options[i].name+": "+ inputs[i].checked);
nickjillings@1324 684 }
nickjillings@1324 685 } else if (node.specification.type == "radio") {
nickjillings@1526 686 var optHold = this.popupResponse;
nickjillings@1324 687 console.log("Radio: "+ node.specification.statement);
nickjillings@1324 688 node.response = null;
nickjillings@1589 689 var i=0;
nickjillings@1324 690 var inputs = optHold.getElementsByTagName('input');
nickjillings@1324 691 while(node.response == null) {
nickjillings@1324 692 if (i == inputs.length)
nickjillings@1324 693 {
nickjillings@1324 694 if (node.specification.mandatory == true)
nickjillings@1324 695 {
nickjillings@1324 696 alert("This radio is mandatory");
nickjillings@1324 697 } else {
nickjillings@1324 698 node.response = -1;
nickjillings@1324 699 }
nickjillings@1324 700 return;
nickjillings@1324 701 }
nickjillings@1324 702 if (inputs[i].checked == true) {
nickjillings@1324 703 node.response = node.specification.options[i];
nickjillings@1324 704 console.log("Selected: "+ node.specification.options[i].name);
nickjillings@1589 705 }
nickjillings@1589 706 i++;
nickjillings@1589 707 }
nickjillings@1324 708 } else if (node.specification.type == "number") {
nickjillings@1573 709 var input = this.popupContent.getElementsByTagName('input')[0];
nickjillings@1573 710 if (node.mandatory == true && input.value.length == 0) {
nickjillings@1574 711 alert('This question is mandatory. Please enter a number');
nickjillings@1574 712 return;
nickjillings@1574 713 }
nickjillings@1574 714 var enteredNumber = Number(input.value);
nickjillings@2044 715 if (isNaN(enteredNumber)) {
nickjillings@1574 716 alert('Please enter a valid number');
nickjillings@1574 717 return;
nickjillings@1574 718 }
nickjillings@1574 719 if (enteredNumber < node.min && node.min != null) {
nickjillings@1574 720 alert('Number is below the minimum value of '+node.min);
nickjillings@1574 721 return;
nickjillings@1574 722 }
nickjillings@1574 723 if (enteredNumber > node.max && node.max != null) {
nickjillings@1574 724 alert('Number is above the maximum value of '+node.max);
nickjillings@1573 725 return;
nickjillings@1573 726 }
nickjillings@1324 727 node.response = input.value;
nickjillings@1622 728 }
nickjillings@1622 729 this.currentIndex++;
nickjillings@1622 730 if (this.currentIndex < this.popupOptions.length) {
nickjillings@1622 731 this.postNode();
nickjillings@1622 732 } else {
nickjillings@1622 733 // Reached the end of the popupOptions
nickjillings@1622 734 this.hidePopup();
nickjillings@1324 735 for (var node of this.popupOptions)
nickjillings@1324 736 {
nickjillings@1324 737 this.store.postResult(node);
nickjillings@1634 738 }
nickjillings@1622 739 advanceState();
nickjillings@1622 740 }
nickjillings@2015 741 };
nickjillings@2034 742
nickjillings@2034 743 this.previousClick = function() {
nickjillings@2034 744 // Triggered when the 'Back' button is clicked in the survey
nickjillings@2034 745 if (this.currentIndex > 0) {
nickjillings@2034 746 this.currentIndex--;
nickjillings@2034 747 this.postNode();
nickjillings@2034 748 }
nickjillings@2034 749 };
nickjillings@1421 750
nickjillings@1421 751 this.resize = function(event)
nickjillings@1421 752 {
nickjillings@1421 753 // Called on window resize;
nickjillings@1344 754 if (this.popup != null) {
nickjillings@1344 755 this.popup.style.left = (window.innerWidth/2)-250 + 'px';
nickjillings@1344 756 this.popup.style.top = (window.innerHeight/2)-125 + 'px';
nickjillings@1344 757 var blank = document.getElementsByClassName('testHalt')[0];
nickjillings@1344 758 blank.style.width = window.innerWidth;
nickjillings@1344 759 blank.style.height = window.innerHeight;
nickjillings@1344 760 }
nickjillings@1421 761 };
nickjillings@2106 762 this.hideNextButton = function() {
nickjillings@2106 763 this.buttonProceed.style.visibility = "hidden";
nickjillings@2106 764 }
nickjillings@2106 765 this.hidePreviousButton = function() {
nickjillings@2106 766 this.buttonPrevious.style.visibility = "hidden";
nickjillings@2106 767 }
nickjillings@2106 768 this.showNextButton = function() {
nickjillings@2106 769 this.buttonProceed.style.visibility = "visible";
nickjillings@2106 770 }
nickjillings@2106 771 this.showPreviousButton = function() {
nickjillings@2106 772 this.buttonPrevious.style.visibility = "visible";
nickjillings@2106 773 }
nickjillings@1621 774 }
nickjillings@1621 775
nickjillings@1622 776 function advanceState()
nickjillings@1621 777 {
nickjillings@1634 778 // Just for complete clarity
nickjillings@1634 779 testState.advanceState();
nickjillings@1634 780 }
nickjillings@1634 781
nickjillings@1634 782 function stateMachine()
nickjillings@1634 783 {
nickjillings@1634 784 // Object prototype for tracking and managing the test state
nickjillings@1634 785 this.stateMap = [];
nickjillings@1324 786 this.preTestSurvey = null;
nickjillings@1324 787 this.postTestSurvey = null;
nickjillings@1634 788 this.stateIndex = null;
nickjillings@1324 789 this.currentStateMap = null;
nickjillings@1324 790 this.currentStatePosition = null;
nickjillings@1354 791 this.currentStore = null;
nickjillings@1634 792 this.initialise = function(){
nickjillings@1324 793
nickjillings@1324 794 // Get the data from Specification
nickjillings@1324 795 var pageHolder = [];
nickjillings@1324 796 for (var page of specification.pages)
nickjillings@1324 797 {
nickjillings@1380 798 var repeat = page.repeatCount;
nickjillings@1380 799 while(repeat >= 0)
nickjillings@1380 800 {
nickjillings@1380 801 pageHolder.push(page);
nickjillings@1380 802 repeat--;
nickjillings@1380 803 }
nickjillings@1324 804 }
nickjillings@1324 805 if (specification.randomiseOrder)
nickjillings@1324 806 {
nickjillings@1324 807 pageHolder = randomiseOrder(pageHolder);
nickjillings@1324 808 }
nickjillings@1324 809 for (var i=0; i<pageHolder.length; i++)
nickjillings@1324 810 {
nickjillings@1324 811 pageHolder[i].presentedId = i;
nickjillings@1324 812 }
nickjillings@1324 813 for (var i=0; i<specification.pages.length; i++)
nickjillings@1324 814 {
nickjillings@2124 815 if (specification.testPages <= i && specification.testPages != 0) {break;}
nickjillings@1324 816 this.stateMap.push(pageHolder[i]);
nickjillings@2149 817 storage.createTestPageStore(pageHolder[i]);
nickjillings@1324 818 }
nickjillings@2124 819
nickjillings@1324 820 if (specification.preTest != null) {this.preTestSurvey = specification.preTest;}
nickjillings@1324 821 if (specification.postTest != null) {this.postTestSurvey = specification.postTest;}
nickjillings@1324 822
nickjillings@1634 823 if (this.stateMap.length > 0) {
nickjillings@1634 824 if(this.stateIndex != null) {
nickjillings@1634 825 console.log('NOTE - State already initialise');
nickjillings@1634 826 }
nickjillings@1634 827 this.stateIndex = -1;
nickjillings@1634 828 } else {
b@2059 829 console.log('FATAL - StateMap not correctly constructed. EMPTY_STATE_MAP');
nickjillings@1622 830 }
nickjillings@1634 831 };
nickjillings@1634 832 this.advanceState = function(){
nickjillings@1634 833 if (this.stateIndex == null) {
nickjillings@1634 834 this.initialise();
nickjillings@1634 835 }
nickjillings@2150 836 storage.update();
nickjillings@1634 837 if (this.stateIndex == -1) {
nickjillings@1342 838 this.stateIndex++;
nickjillings@1634 839 console.log('Starting test...');
nickjillings@1324 840 if (this.preTestSurvey != null)
nickjillings@1324 841 {
nickjillings@1324 842 popup.initState(this.preTestSurvey,storage.globalPreTest);
nickjillings@1342 843 } else {
nickjillings@1342 844 this.advanceState();
nickjillings@1318 845 }
nickjillings@1324 846 } else if (this.stateIndex == this.stateMap.length)
nickjillings@1324 847 {
nickjillings@1324 848 // All test pages complete, post test
nickjillings@1324 849 console.log('Ending test ...');
nickjillings@1324 850 this.stateIndex++;
nickjillings@1324 851 if (this.postTestSurvey == null) {
nickjillings@1324 852 this.advanceState();
nickjillings@1318 853 } else {
nickjillings@1324 854 popup.initState(this.postTestSurvey,storage.globalPostTest);
nickjillings@1324 855 }
nickjillings@1324 856 } else if (this.stateIndex > this.stateMap.length)
nickjillings@1324 857 {
nickjillings@1324 858 createProjectSave(specification.projectReturn);
nickjillings@1634 859 }
nickjillings@1324 860 else
nickjillings@1324 861 {
nickjillings@1324 862 if (this.currentStateMap == null)
nickjillings@1324 863 {
nickjillings@1318 864 this.currentStateMap = this.stateMap[this.stateIndex];
nickjillings@1334 865 if (this.currentStateMap.randomiseOrder)
nickjillings@1334 866 {
nickjillings@1334 867 this.currentStateMap.audioElements = randomiseOrder(this.currentStateMap.audioElements);
nickjillings@1334 868 }
nickjillings@2149 869 this.currentStore = storage.testPages[this.stateIndex];
nickjillings@1324 870 if (this.currentStateMap.preTest != null)
nickjillings@1324 871 {
nickjillings@1324 872 this.currentStatePosition = 'pre';
nickjillings@1324 873 popup.initState(this.currentStateMap.preTest,storage.testPages[this.stateIndex].preTest);
nickjillings@1318 874 } else {
nickjillings@1324 875 this.currentStatePosition = 'test';
nickjillings@1324 876 }
nickjillings@1324 877 interfaceContext.newPage(this.currentStateMap,storage.testPages[this.stateIndex]);
nickjillings@1324 878 return;
nickjillings@1634 879 }
nickjillings@1324 880 switch(this.currentStatePosition)
nickjillings@1324 881 {
nickjillings@1324 882 case 'pre':
nickjillings@1324 883 this.currentStatePosition = 'test';
nickjillings@1324 884 break;
nickjillings@1324 885 case 'test':
nickjillings@1324 886 this.currentStatePosition = 'post';
nickjillings@1324 887 // Save the data
nickjillings@1324 888 this.testPageCompleted();
nickjillings@1324 889 if (this.currentStateMap.postTest == null)
nickjillings@1324 890 {
nickjillings@1318 891 this.advanceState();
nickjillings@1324 892 return;
nickjillings@1634 893 } else {
nickjillings@1324 894 popup.initState(this.currentStateMap.postTest,storage.testPages[this.stateIndex].postTest);
nickjillings@1634 895 }
nickjillings@1324 896 break;
nickjillings@1324 897 case 'post':
nickjillings@1324 898 this.stateIndex++;
nickjillings@1324 899 this.currentStateMap = null;
nickjillings@2149 900 this.currentStore.finish();
nickjillings@1324 901 this.advanceState();
nickjillings@1324 902 break;
nickjillings@1324 903 };
nickjillings@1634 904 }
nickjillings@1634 905 };
nickjillings@1634 906
nickjillings@1324 907 this.testPageCompleted = function() {
nickjillings@1634 908 // Function called each time a test page has been completed
nickjillings@1324 909 var storePoint = storage.testPages[this.stateIndex];
nickjillings@1324 910 // First get the test metric
nickjillings@1324 911
nickjillings@1324 912 var metric = storePoint.XMLDOM.getElementsByTagName('metric')[0];
nickjillings@1412 913 if (audioEngineContext.metric.enableTestTimer)
nickjillings@1412 914 {
nickjillings@1324 915 var testTime = storePoint.parent.document.createElement('metricresult');
nickjillings@1412 916 testTime.id = 'testTime';
nickjillings@1412 917 testTime.textContent = audioEngineContext.timer.testDuration;
nickjillings@1412 918 metric.appendChild(testTime);
nickjillings@1412 919 }
nickjillings@1324 920
nickjillings@1412 921 var audioObjects = audioEngineContext.audioObjects;
nickjillings@1324 922 for (var ao of audioEngineContext.audioObjects)
nickjillings@1412 923 {
nickjillings@1324 924 ao.exportXMLDOM();
nickjillings@1412 925 }
nickjillings@1324 926 for (var element of interfaceContext.commentQuestions)
nickjillings@1324 927 {
nickjillings@1324 928 element.exportXMLDOM(storePoint);
nickjillings@1324 929 }
nickjillings@1324 930 pageXMLSave(storePoint.XMLDOM, this.currentStateMap);
nickjillings@1576 931 };
nickjillings@1621 932 }
nickjillings@1621 933
nickjillings@1408 934 function AudioEngine(specification) {
nickjillings@1682 935
nickjillings@1682 936 // Create two output paths, the main outputGain and fooGain.
nickjillings@1682 937 // Output gain is default to 1 and any items for playback route here
nickjillings@1682 938 // Foo gain is used for analysis to ensure paths get processed, but are not heard
nickjillings@1682 939 // because web audio will optimise and any route which does not go to the destination gets ignored.
nickjillings@1682 940 this.outputGain = audioContext.createGain();
nickjillings@1682 941 this.fooGain = audioContext.createGain();
nickjillings@1682 942 this.fooGain.gain = 0;
nickjillings@1682 943
nickjillings@1688 944 // Use this to detect playback state: 0 - stopped, 1 - playing
nickjillings@1688 945 this.status = 0;
nickjillings@1688 946
nickjillings@1682 947 // Connect both gains to output
nickjillings@1682 948 this.outputGain.connect(audioContext.destination);
nickjillings@1682 949 this.fooGain.connect(audioContext.destination);
nickjillings@1682 950
nickjillings@1659 951 // Create the timer Object
nickjillings@1659 952 this.timer = new timer();
nickjillings@1659 953 // Create session metrics
nickjillings@1408 954 this.metric = new sessionMetrics(this,specification);
nickjillings@1659 955
nickjillings@1667 956 this.loopPlayback = false;
nickjillings@1667 957
nickjillings@1324 958 this.pageStore = null;
nickjillings@1324 959
nickjillings@1682 960 // Create store for new audioObjects
nickjillings@1682 961 this.audioObjects = [];
nickjillings@1682 962
nickjillings@1410 963 this.buffers = [];
nickjillings@1430 964 this.bufferObj = function()
nickjillings@1410 965 {
nickjillings@1430 966 this.url = null;
nickjillings@1410 967 this.buffer = null;
nickjillings@1410 968 this.xmlRequest = new XMLHttpRequest();
nickjillings@1396 969 this.xmlRequest.parent = this;
nickjillings@1410 970 this.users = [];
nickjillings@1316 971 this.progress = 0;
nickjillings@1316 972 this.status = 0;
nickjillings@1342 973 this.ready = function()
nickjillings@1342 974 {
nickjillings@1316 975 if (this.status >= 2)
nickjillings@1316 976 {
nickjillings@1316 977 this.status = 3;
nickjillings@1316 978 }
nickjillings@1342 979 for (var i=0; i<this.users.length; i++)
nickjillings@1342 980 {
nickjillings@1342 981 this.users[i].state = 1;
nickjillings@1342 982 if (this.users[i].interfaceDOM != null)
nickjillings@1342 983 {
nickjillings@1342 984 this.users[i].bufferLoaded(this);
nickjillings@1342 985 }
nickjillings@1342 986 }
nickjillings@1342 987 };
nickjillings@1430 988 this.getMedia = function(url) {
nickjillings@1430 989 this.url = url;
nickjillings@1430 990 this.xmlRequest.open('GET',this.url,true);
nickjillings@1430 991 this.xmlRequest.responseType = 'arraybuffer';
nickjillings@1430 992
nickjillings@1430 993 var bufferObj = this;
nickjillings@1430 994
nickjillings@1430 995 // Create callback to decode the data asynchronously
nickjillings@1430 996 this.xmlRequest.onloadend = function() {
nickjillings@1353 997 // Use inbuilt WAVE decoder first
nickjillings@2113 998 if (this.status == -1) {return;}
nickjillings@1353 999 var waveObj = new WAVE();
nickjillings@1353 1000 if (waveObj.open(bufferObj.xmlRequest.response) == 0)
nickjillings@1353 1001 {
nickjillings@1353 1002 bufferObj.buffer = audioContext.createBuffer(waveObj.num_channels,waveObj.num_samples,waveObj.sample_rate);
nickjillings@1353 1003 for (var c=0; c<waveObj.num_channels; c++)
nickjillings@1353 1004 {
nickjillings@1353 1005 var buffer_ptr = bufferObj.buffer.getChannelData(c);
nickjillings@1353 1006 for (var n=0; n<waveObj.num_samples; n++)
nickjillings@1353 1007 {
nickjillings@1353 1008 buffer_ptr[n] = waveObj.decoded_data[c][n];
nickjillings@1353 1009 }
nickjillings@1353 1010 }
nickjillings@1316 1011
nickjillings@1353 1012 delete waveObj;
nickjillings@1353 1013 } else {
nickjillings@1353 1014 audioContext.decodeAudioData(bufferObj.xmlRequest.response, function(decodedData) {
nickjillings@1353 1015 bufferObj.buffer = decodedData;
nickjillings@1353 1016 }, function(e){
nickjillings@1353 1017 // Should only be called if there was an error, but sometimes gets called continuously
nickjillings@1353 1018 // Check here if the error is genuine
nickjillings@1353 1019 if (bufferObj.xmlRequest.response == undefined) {
nickjillings@1353 1020 // Genuine error
nickjillings@1353 1021 console.log('FATAL - Error loading buffer on '+audioObj.id);
nickjillings@1353 1022 if (request.status == 404)
nickjillings@1353 1023 {
nickjillings@1353 1024 console.log('FATAL - Fragment '+audioObj.id+' 404 error');
nickjillings@1353 1025 console.log('URL: '+audioObj.url);
nickjillings@1353 1026 errorSessionDump('Fragment '+audioObj.id+' 404 error');
nickjillings@1353 1027 }
nickjillings@2113 1028 this.parent.status = -1;
nickjillings@1353 1029 }
nickjillings@1353 1030 });
nickjillings@1353 1031 }
nickjillings@1353 1032 if (bufferObj.buffer != undefined)
nickjillings@1353 1033 {
nickjillings@1316 1034 bufferObj.status = 2;
nickjillings@1353 1035 calculateLoudness(bufferObj,"I");
nickjillings@1353 1036 }
nickjillings@1430 1037 };
nickjillings@2113 1038
nickjillings@2113 1039 // Create callback for any error in loading
nickjillings@2113 1040 this.xmlRequest.onerror = function() {
nickjillings@2113 1041 this.parent.status = -1;
nickjillings@2113 1042 for (var i=0; i<this.parent.users.length; i++)
nickjillings@2113 1043 {
nickjillings@2113 1044 this.parent.users[i].state = -1;
nickjillings@2113 1045 if (this.parent.users[i].interfaceDOM != null)
nickjillings@2113 1046 {
nickjillings@2113 1047 this.parent.users[i].bufferLoaded(this);
nickjillings@2113 1048 }
nickjillings@2113 1049 }
nickjillings@2113 1050 }
nickjillings@2113 1051
nickjillings@1433 1052 this.progress = 0;
nickjillings@1433 1053 this.progressCallback = function(event){
nickjillings@1433 1054 if (event.lengthComputable)
nickjillings@1433 1055 {
nickjillings@1396 1056 this.parent.progress = event.loaded / event.total;
nickjillings@1396 1057 for (var i=0; i<this.parent.users.length; i++)
nickjillings@1396 1058 {
nickjillings@1396 1059 if(this.parent.users[i].interfaceDOM != null)
nickjillings@1396 1060 {
nickjillings@1396 1061 if (typeof this.parent.users[i].interfaceDOM.updateLoading === "function")
nickjillings@1396 1062 {
nickjillings@1396 1063 this.parent.users[i].interfaceDOM.updateLoading(this.parent.progress*100);
nickjillings@1396 1064 }
nickjillings@1396 1065 }
nickjillings@1396 1066 }
nickjillings@1433 1067 }
nickjillings@1433 1068 };
nickjillings@1433 1069 this.xmlRequest.addEventListener("progress", this.progressCallback);
nickjillings@1316 1070 this.status = 1;
nickjillings@1430 1071 this.xmlRequest.send();
nickjillings@1410 1072 };
nickjillings@1316 1073
nickjillings@1316 1074 this.registerAudioObject = function(audioObject)
nickjillings@1316 1075 {
nickjillings@1316 1076 // Called by an audioObject to register to the buffer for use
nickjillings@1316 1077 // First check if already in the register pool
nickjillings@1316 1078 for (var objects of this.users)
nickjillings@1316 1079 {
nickjillings@1316 1080 if (audioObject.id == objects.id){return 0;}
nickjillings@1316 1081 }
nickjillings@1316 1082 this.users.push(audioObject);
nickjillings@2113 1083 if (this.status == 3 || this.status == -1)
nickjillings@1316 1084 {
nickjillings@1316 1085 // The buffer is already ready, trigger bufferLoaded
nickjillings@1316 1086 audioObject.bufferLoaded(this);
nickjillings@1316 1087 }
nickjillings@1316 1088 }
nickjillings@1410 1089 };
nickjillings@1410 1090
nickjillings@1565 1091 this.play = function(id) {
nickjillings@1620 1092 // Start the timer and set the audioEngine state to playing (1)
nickjillings@1523 1093 if (this.status == 0 && this.loopPlayback) {
nickjillings@1620 1094 // Check if all audioObjects are ready
nickjillings@1523 1095 if(this.checkAllReady())
nickjillings@1523 1096 {
nickjillings@1565 1097 this.status = 1;
nickjillings@1523 1098 this.setSynchronousLoop();
nickjillings@1565 1099 }
nickjillings@1565 1100 }
nickjillings@1523 1101 else
nickjillings@1523 1102 {
nickjillings@1523 1103 this.status = 1;
nickjillings@1523 1104 }
nickjillings@1565 1105 if (this.status== 1) {
nickjillings@1523 1106 this.timer.startTest();
nickjillings@1567 1107 if (id == undefined) {
nickjillings@1567 1108 id = -1;
nickjillings@1523 1109 console.log('FATAL - Passed id was undefined - AudioEngineContext.play(id)');
nickjillings@1523 1110 return;
nickjillings@1567 1111 } else {
nickjillings@1567 1112 interfaceContext.playhead.setTimePerPixel(this.audioObjects[id]);
nickjillings@1567 1113 }
nickjillings@1565 1114 if (this.loopPlayback) {
nickjillings@2099 1115 var setTime = audioContext.currentTime;
nickjillings@1565 1116 for (var i=0; i<this.audioObjects.length; i++)
nickjillings@1565 1117 {
nickjillings@2099 1118 this.audioObjects[i].play(setTime);
nickjillings@1565 1119 if (id == i) {
nickjillings@1360 1120 this.audioObjects[i].loopStart(setTime);
nickjillings@1565 1121 } else {
nickjillings@1360 1122 this.audioObjects[i].loopStop(setTime);
nickjillings@1636 1123 }
nickjillings@1636 1124 }
nickjillings@1565 1125 } else {
nickjillings@1360 1126 var setTime = audioContext.currentTime+0.1;
nickjillings@1565 1127 for (var i=0; i<this.audioObjects.length; i++)
nickjillings@1565 1128 {
nickjillings@1565 1129 if (i != id) {
nickjillings@1360 1130 this.audioObjects[i].stop(setTime);
nickjillings@1565 1131 } else if (i == id) {
nickjillings@1360 1132 this.audioObjects[id].play(setTime);
nickjillings@1565 1133 }
nickjillings@1565 1134 }
nickjillings@1620 1135 }
nickjillings@1567 1136 interfaceContext.playhead.start();
nickjillings@1620 1137 }
nickjillings@1620 1138 };
nickjillings@1682 1139
nickjillings@1620 1140 this.stop = function() {
nickjillings@1378 1141 // Send stop and reset command to all playback buffers
nickjillings@1620 1142 if (this.status == 1) {
nickjillings@1360 1143 var setTime = audioContext.currentTime+0.1;
nickjillings@1620 1144 for (var i=0; i<this.audioObjects.length; i++)
nickjillings@1620 1145 {
nickjillings@1360 1146 this.audioObjects[i].stop(setTime);
nickjillings@1620 1147 }
nickjillings@1567 1148 interfaceContext.playhead.stop();
nickjillings@1620 1149 }
nickjillings@1620 1150 };
nickjillings@1689 1151
nickjillings@1582 1152 this.newTrack = function(element) {
nickjillings@1682 1153 // Pull data from given URL into new audio buffer
nickjillings@1682 1154 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin'
nickjillings@1688 1155
nickjillings@1682 1156 // Create the audioObject with ID of the new track length;
nickjillings@1659 1157 audioObjectId = this.audioObjects.length;
nickjillings@1682 1158 this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
nickjillings@1688 1159
nickjillings@1410 1160 // Check if audioObject buffer is currently stored by full URL
nickjillings@1324 1161 var URL = testState.currentStateMap.hostURL + element.url;
nickjillings@1410 1162 var buffer = null;
nickjillings@1410 1163 for (var i=0; i<this.buffers.length; i++)
nickjillings@1410 1164 {
nickjillings@1410 1165 if (URL == this.buffers[i].url)
nickjillings@1410 1166 {
nickjillings@1410 1167 buffer = this.buffers[i];
nickjillings@1410 1168 break;
nickjillings@1410 1169 }
nickjillings@1410 1170 }
nickjillings@1410 1171 if (buffer == null)
nickjillings@1410 1172 {
nickjillings@1426 1173 console.log("[WARN]: Buffer was not loaded in pre-test! "+URL);
nickjillings@1430 1174 buffer = new this.bufferObj();
nickjillings@1316 1175 this.buffers.push(buffer);
nickjillings@1430 1176 buffer.getMedia(URL);
nickjillings@1410 1177 }
nickjillings@1582 1178 this.audioObjects[audioObjectId].specification = element;
nickjillings@1426 1179 this.audioObjects[audioObjectId].url = URL;
nickjillings@1324 1180 // Obtain store node
nickjillings@1324 1181 var aeNodes = this.pageStore.XMLDOM.getElementsByTagName('audioelement');
nickjillings@1324 1182 for (var i=0; i<aeNodes.length; i++)
nickjillings@1410 1183 {
nickjillings@1324 1184 if(aeNodes[i].id == element.id)
nickjillings@1324 1185 {
nickjillings@1324 1186 this.audioObjects[audioObjectId].storeDOM = aeNodes[i];
nickjillings@1324 1187 break;
nickjillings@1324 1188 }
nickjillings@1410 1189 }
nickjillings@1316 1190 buffer.registerAudioObject(this.audioObjects[audioObjectId]);
nickjillings@1579 1191 return this.audioObjects[audioObjectId];
nickjillings@1697 1192 };
nickjillings@1682 1193
nickjillings@1369 1194 this.newTestPage = function(audioHolderObject,store) {
nickjillings@1324 1195 this.pageStore = store;
nickjillings@2127 1196 this.status = 0;
nickjillings@1620 1197 this.audioObjectsReady = false;
nickjillings@1620 1198 this.metric.reset();
nickjillings@1410 1199 for (var i=0; i < this.buffers.length; i++)
nickjillings@1410 1200 {
nickjillings@1410 1201 this.buffers[i].users = [];
nickjillings@1410 1202 }
nickjillings@1620 1203 this.audioObjects = [];
nickjillings@1369 1204 this.timer = new timer();
nickjillings@1369 1205 this.loopPlayback = audioHolderObject.loop;
nickjillings@1620 1206 };
nickjillings@1620 1207
nickjillings@1614 1208 this.checkAllPlayed = function() {
nickjillings@1614 1209 arr = [];
nickjillings@1614 1210 for (var id=0; id<this.audioObjects.length; id++) {
nickjillings@2008 1211 if (this.audioObjects[id].metric.wasListenedTo == false) {
nickjillings@1614 1212 arr.push(this.audioObjects[id].id);
nickjillings@1614 1213 }
nickjillings@1614 1214 }
nickjillings@1614 1215 return arr;
nickjillings@1614 1216 };
nickjillings@1614 1217
nickjillings@1620 1218 this.checkAllReady = function() {
nickjillings@1620 1219 var ready = true;
nickjillings@1620 1220 for (var i=0; i<this.audioObjects.length; i++) {
nickjillings@1620 1221 if (this.audioObjects[i].state == 0) {
nickjillings@1620 1222 // Track not ready
nickjillings@1620 1223 console.log('WAIT -- audioObject '+i+' not ready yet!');
nickjillings@1620 1224 ready = false;
nickjillings@1620 1225 };
nickjillings@1620 1226 }
nickjillings@1620 1227 return ready;
nickjillings@1620 1228 };
nickjillings@1620 1229
nickjillings@1535 1230 this.setSynchronousLoop = function() {
nickjillings@1535 1231 // Pads the signals so they are all exactly the same length
nickjillings@1523 1232 var length = 0;
nickjillings@1523 1233 var maxId;
nickjillings@1523 1234 for (var i=0; i<this.audioObjects.length; i++)
nickjillings@1535 1235 {
nickjillings@1413 1236 if (length < this.audioObjects[i].buffer.buffer.length)
nickjillings@1535 1237 {
nickjillings@1413 1238 length = this.audioObjects[i].buffer.buffer.length;
nickjillings@1523 1239 maxId = i;
nickjillings@1535 1240 }
nickjillings@1523 1241 }
nickjillings@1404 1242 // Extract the audio and zero-pad
nickjillings@1430 1243 for (var i=0; i<this.audioObjects.length; i++)
nickjillings@1523 1244 {
nickjillings@1413 1245 var orig = this.audioObjects[i].buffer.buffer;
nickjillings@1523 1246 var hold = audioContext.createBuffer(orig.numberOfChannels,length,orig.sampleRate);
nickjillings@1523 1247 for (var c=0; c<orig.numberOfChannels; c++)
nickjillings@1535 1248 {
nickjillings@1523 1249 var inData = hold.getChannelData(c);
nickjillings@1523 1250 var outData = orig.getChannelData(c);
nickjillings@1523 1251 for (var n=0; n<orig.length; n++)
nickjillings@1523 1252 {inData[n] = outData[n];}
nickjillings@1535 1253 }
nickjillings@1320 1254 hold.playbackGain = orig.playbackGain;
nickjillings@1430 1255 hold.lufs = orig.lufs;
nickjillings@1413 1256 this.audioObjects[i].buffer.buffer = hold;
nickjillings@1535 1257 }
nickjillings@1535 1258 };
nickjillings@1370 1259
nickjillings@1370 1260 this.exportXML = function()
nickjillings@1370 1261 {
nickjillings@1370 1262
nickjillings@1370 1263 };
nickjillings@1535 1264
nickjillings@1682 1265 }
nickjillings@1682 1266
nickjillings@1682 1267 function audioObject(id) {
nickjillings@1682 1268 // The main buffer object with common control nodes to the AudioEngine
nickjillings@1682 1269
nickjillings@1582 1270 this.specification;
nickjillings@1682 1271 this.id = id;
nickjillings@1682 1272 this.state = 0; // 0 - no data, 1 - ready
nickjillings@1704 1273 this.url = null; // Hold the URL given for the output back to the results.
nickjillings@1602 1274 this.metric = new metricTracker(this);
nickjillings@1324 1275 this.storeDOM = null;
nickjillings@1682 1276
nickjillings@1577 1277 // Bindings for GUI
nickjillings@1583 1278 this.interfaceDOM = null;
nickjillings@1577 1279 this.commentDOM = null;
nickjillings@1577 1280
nickjillings@1682 1281 // Create a buffer and external gain control to allow internal patching of effects and volume leveling.
nickjillings@1667 1282 this.bufferNode = undefined;
nickjillings@1682 1283 this.outputGain = audioContext.createGain();
nickjillings@1682 1284
nickjillings@1324 1285 this.onplayGain = 1.0;
nickjillings@1689 1286
nickjillings@1682 1287 // Connect buffer to the audio graph
nickjillings@1682 1288 this.outputGain.connect(audioEngineContext.outputGain);
nickjillings@1682 1289
nickjillings@1682 1290 // the audiobuffer is not designed for multi-start playback
nickjillings@1682 1291 // When stopeed, the buffer node is deleted and recreated with the stored buffer.
nickjillings@1682 1292 this.buffer;
nickjillings@1434 1293
nickjillings@1434 1294 this.bufferLoaded = function(callee)
nickjillings@1434 1295 {
nickjillings@1434 1296 // Called by the associated buffer when it has finished loading, will then 'bind' the buffer to the
nickjillings@1434 1297 // audioObject and trigger the interfaceDOM.enable() function for user feedback
nickjillings@2113 1298 if (callee.status == -1) {
nickjillings@2113 1299 // ERROR
nickjillings@2113 1300 this.state = -1;
nickjillings@2113 1301 if (this.interfaceDOM != null) {this.interfaceDOM.error();}
nickjillings@2113 1302 this.buffer = callee;
nickjillings@2113 1303 return;
nickjillings@2113 1304 }
nickjillings@1434 1305 if (audioEngineContext.loopPlayback){
nickjillings@1434 1306 // First copy the buffer into this.buffer
nickjillings@1434 1307 this.buffer = new audioEngineContext.bufferObj();
nickjillings@1434 1308 this.buffer.url = callee.url;
nickjillings@1434 1309 this.buffer.buffer = audioContext.createBuffer(callee.buffer.numberOfChannels, callee.buffer.length, callee.buffer.sampleRate);
nickjillings@1434 1310 for (var c=0; c<callee.buffer.numberOfChannels; c++)
nickjillings@1434 1311 {
nickjillings@1434 1312 var src = callee.buffer.getChannelData(c);
nickjillings@1434 1313 var dst = this.buffer.buffer.getChannelData(c);
nickjillings@1434 1314 for (var n=0; n<src.length; n++)
nickjillings@1434 1315 {
nickjillings@1434 1316 dst[n] = src[n];
nickjillings@1434 1317 }
nickjillings@1434 1318 }
nickjillings@1434 1319 } else {
nickjillings@1434 1320 this.buffer = callee;
nickjillings@1434 1321 }
nickjillings@1434 1322 this.state = 1;
nickjillings@1320 1323 this.buffer.buffer.playbackGain = callee.buffer.playbackGain;
nickjillings@1434 1324 this.buffer.buffer.lufs = callee.buffer.lufs;
nickjillings@1348 1325 var targetLUFS = this.specification.parent.loudness || specification.loudness;
nickjillings@1434 1326 if (typeof targetLUFS === "number")
nickjillings@1434 1327 {
nickjillings@1320 1328 this.buffer.buffer.playbackGain = decibelToLinear(targetLUFS - this.buffer.buffer.lufs);
nickjillings@1434 1329 } else {
nickjillings@1320 1330 this.buffer.buffer.playbackGain = 1.0;
nickjillings@1434 1331 }
nickjillings@1434 1332 if (this.interfaceDOM != null) {
nickjillings@1434 1333 this.interfaceDOM.enable();
nickjillings@1434 1334 }
nickjillings@1324 1335 this.onplayGain = decibelToLinear(this.specification.gain)*this.buffer.buffer.playbackGain;
nickjillings@1324 1336 this.storeDOM.setAttribute('playGain',linearToDecibel(this.onplayGain));
nickjillings@1318 1337 };
nickjillings@1325 1338
nickjillings@1325 1339 this.bindInterface = function(interfaceObject)
nickjillings@1325 1340 {
nickjillings@1325 1341 this.interfaceDOM = interfaceObject;
nickjillings@1325 1342 this.metric.initialise(interfaceObject.getValue());
nickjillings@1325 1343 if (this.state == 1)
nickjillings@1325 1344 {
nickjillings@1325 1345 this.interfaceDOM.enable();
nickjillings@2113 1346 } else if (this.state == -1) {
nickjillings@2113 1347 // ERROR
nickjillings@2113 1348 this.interfaceDOM.error();
nickjillings@2113 1349 return;
nickjillings@2113 1350 }
nickjillings@1338 1351 this.storeDOM.setAttribute('presentedId',interfaceObject.getPresentedId());
nickjillings@1434 1352 };
b@1639 1353
nickjillings@1360 1354 this.loopStart = function(setTime) {
nickjillings@1360 1355 this.outputGain.gain.linearRampToValueAtTime(this.onplayGain,setTime);
nickjillings@1637 1356 this.metric.startListening(audioEngineContext.timer.getTestTime());
nickjillings@1360 1357 this.interfaceDOM.startPlayback();
nickjillings@1577 1358 };
nickjillings@1637 1359
nickjillings@1360 1360 this.loopStop = function(setTime) {
nickjillings@1637 1361 if (this.outputGain.gain.value != 0.0) {
nickjillings@1360 1362 this.outputGain.gain.linearRampToValueAtTime(0.0,setTime);
nickjillings@1637 1363 this.metric.stopListening(audioEngineContext.timer.getTestTime());
nickjillings@1637 1364 }
nickjillings@1360 1365 this.interfaceDOM.stopPlayback();
nickjillings@1577 1366 };
nickjillings@1637 1367
nickjillings@1682 1368 this.play = function(startTime) {
nickjillings@1410 1369 if (this.bufferNode == undefined && this.buffer.buffer != undefined) {
nickjillings@1565 1370 this.bufferNode = audioContext.createBufferSource();
nickjillings@1565 1371 this.bufferNode.owner = this;
nickjillings@1565 1372 this.bufferNode.connect(this.outputGain);
nickjillings@1410 1373 this.bufferNode.buffer = this.buffer.buffer;
nickjillings@1565 1374 this.bufferNode.loop = audioEngineContext.loopPlayback;
nickjillings@1522 1375 this.bufferNode.onended = function(event) {
nickjillings@1565 1376 // Safari does not like using 'this' to reference the calling object!
nickjillings@1458 1377 //event.currentTarget.owner.metric.stopListening(audioEngineContext.timer.getTestTime(),event.currentTarget.owner.getCurrentPosition());
nickjillings@2120 1378 if (event.currentTarget != null) {
nickjillings@2120 1379 event.currentTarget.owner.stop(audioContext.currentTime+1);
nickjillings@2120 1380 }
nickjillings@1565 1381 };
nickjillings@1565 1382 if (this.bufferNode.loop == false) {
nickjillings@1565 1383 this.metric.startListening(audioEngineContext.timer.getTestTime());
nickjillings@1360 1384 this.outputGain.gain.setValueAtTime(this.onplayGain,startTime);
nickjillings@1360 1385 this.interfaceDOM.startPlayback();
nickjillings@1360 1386 } else {
nickjillings@1360 1387 this.outputGain.gain.setValueAtTime(0.0,startTime);
nickjillings@1360 1388 }
nickjillings@1565 1389 this.bufferNode.start(startTime);
nickjillings@2126 1390 this.bufferNode.playbackStartTime = audioEngineContext.timer.getTestTime();
nickjillings@1617 1391 }
nickjillings@1697 1392 };
nickjillings@1682 1393
nickjillings@1360 1394 this.stop = function(stopTime) {
nickjillings@1360 1395 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
nickjillings@2002 1396 if (this.bufferNode != undefined)
nickjillings@2002 1397 {
nickjillings@1566 1398 this.metric.stopListening(audioEngineContext.timer.getTestTime(),this.getCurrentPosition());
nickjillings@1360 1399 this.bufferNode.stop(stopTime);
nickjillings@2002 1400 this.bufferNode = undefined;
nickjillings@2002 1401 }
nickjillings@1360 1402 this.outputGain.gain.value = 0.0;
nickjillings@1360 1403 this.interfaceDOM.stopPlayback();
nickjillings@1697 1404 };
nickjillings@2019 1405
nickjillings@2019 1406 this.getCurrentPosition = function() {
nickjillings@2019 1407 var time = audioEngineContext.timer.getTestTime();
nickjillings@2019 1408 if (this.bufferNode != undefined) {
nickjillings@2138 1409 var position = (time - this.bufferNode.playbackStartTime)%this.buffer.buffer.duration;
nickjillings@2138 1410 if (isNaN(position)){return 0;}
nickjillings@2138 1411 return position;
nickjillings@2019 1412 } else {
nickjillings@2019 1413 return 0;
nickjillings@2019 1414 }
nickjillings@2019 1415 };
nickjillings@1688 1416
nickjillings@1583 1417 this.exportXMLDOM = function() {
nickjillings@1324 1418 var file = storage.document.createElement('file');
nickjillings@1416 1419 file.setAttribute('sampleRate',this.buffer.buffer.sampleRate);
nickjillings@1416 1420 file.setAttribute('channels',this.buffer.buffer.numberOfChannels);
nickjillings@1416 1421 file.setAttribute('sampleCount',this.buffer.buffer.length);
nickjillings@1416 1422 file.setAttribute('duration',this.buffer.buffer.duration);
nickjillings@1324 1423 this.storeDOM.appendChild(file);
nickjillings@1324 1424 if (this.specification.type != 'outside-reference') {
nickjillings@1413 1425 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
nickjillings@1340 1426 if (interfaceXML != null)
nickjillings@1340 1427 {
nickjillings@1340 1428 if (interfaceXML.length == undefined) {
nickjillings@1340 1429 this.storeDOM.appendChild(interfaceXML);
nickjillings@1340 1430 } else {
nickjillings@1340 1431 for (var i=0; i<interfaceXML.length; i++)
nickjillings@1340 1432 {
nickjillings@1340 1433 this.storeDOM.appendChild(interfaceXML[i]);
nickjillings@1340 1434 }
nickjillings@1413 1435 }
nickjillings@1413 1436 }
nickjillings@1330 1437 if (this.commentDOM != null) {
nickjillings@1330 1438 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
nickjillings@1529 1439 }
nickjillings@2050 1440 }
nickjillings@1324 1441 var nodes = this.metric.exportXMLDOM();
nickjillings@1324 1442 var mroot = this.storeDOM.getElementsByTagName('metric')[0];
nickjillings@1324 1443 for (var i=0; i<nodes.length; i++)
nickjillings@1324 1444 {
nickjillings@1324 1445 mroot.appendChild(nodes[i]);
nickjillings@1324 1446 }
nickjillings@1583 1447 };
nickjillings@1659 1448 }
nickjillings@1659 1449
nickjillings@1659 1450 function timer()
nickjillings@1659 1451 {
nickjillings@1659 1452 /* Timer object used in audioEngine to keep track of session timings
nickjillings@1659 1453 * Uses the timer of the web audio API, so sample resolution
nickjillings@1659 1454 */
nickjillings@1659 1455 this.testStarted = false;
nickjillings@1659 1456 this.testStartTime = 0;
nickjillings@1659 1457 this.testDuration = 0;
nickjillings@1659 1458 this.minimumTestTime = 0; // No minimum test time
nickjillings@1659 1459 this.startTest = function()
nickjillings@1659 1460 {
nickjillings@1659 1461 if (this.testStarted == false)
nickjillings@1659 1462 {
nickjillings@1659 1463 this.testStartTime = audioContext.currentTime;
nickjillings@1659 1464 this.testStarted = true;
nickjillings@1659 1465 this.updateTestTime();
nickjillings@1662 1466 audioEngineContext.metric.initialiseTest();
nickjillings@1659 1467 }
nickjillings@1659 1468 };
nickjillings@1659 1469 this.stopTest = function()
nickjillings@1659 1470 {
nickjillings@1659 1471 if (this.testStarted)
nickjillings@1659 1472 {
nickjillings@1659 1473 this.testDuration = this.getTestTime();
nickjillings@1659 1474 this.testStarted = false;
nickjillings@1659 1475 } else {
nickjillings@1659 1476 console.log('ERR: Test tried to end before beginning');
nickjillings@1659 1477 }
nickjillings@1659 1478 };
nickjillings@1659 1479 this.updateTestTime = function()
nickjillings@1659 1480 {
nickjillings@1659 1481 if (this.testStarted)
nickjillings@1659 1482 {
nickjillings@1659 1483 this.testDuration = audioContext.currentTime - this.testStartTime;
nickjillings@1659 1484 }
nickjillings@1659 1485 };
nickjillings@1659 1486 this.getTestTime = function()
nickjillings@1659 1487 {
nickjillings@1659 1488 this.updateTestTime();
nickjillings@1659 1489 return this.testDuration;
nickjillings@1659 1490 };
nickjillings@1659 1491 }
nickjillings@1659 1492
nickjillings@1408 1493 function sessionMetrics(engine,specification)
nickjillings@1659 1494 {
nickjillings@1659 1495 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
nickjillings@1659 1496 */
nickjillings@1659 1497 this.engine = engine;
nickjillings@1659 1498 this.lastClicked = -1;
nickjillings@1659 1499 this.data = -1;
nickjillings@1620 1500 this.reset = function() {
nickjillings@1620 1501 this.lastClicked = -1;
nickjillings@1620 1502 this.data = -1;
nickjillings@1620 1503 };
nickjillings@1408 1504
nickjillings@1408 1505 this.enableElementInitialPosition = false;
nickjillings@1408 1506 this.enableElementListenTracker = false;
nickjillings@1408 1507 this.enableElementTimer = false;
nickjillings@1408 1508 this.enableElementTracker = false;
nickjillings@1408 1509 this.enableFlagListenedTo = false;
nickjillings@1408 1510 this.enableFlagMoved = false;
nickjillings@1408 1511 this.enableTestTimer = false;
nickjillings@1408 1512 // Obtain the metrics enabled
nickjillings@1324 1513 for (var i=0; i<specification.metrics.enabled.length; i++)
nickjillings@1408 1514 {
nickjillings@1324 1515 var node = specification.metrics.enabled[i];
nickjillings@1324 1516 switch(node)
nickjillings@1408 1517 {
nickjillings@1408 1518 case 'testTimer':
nickjillings@1408 1519 this.enableTestTimer = true;
nickjillings@1408 1520 break;
nickjillings@1408 1521 case 'elementTimer':
nickjillings@1408 1522 this.enableElementTimer = true;
nickjillings@1408 1523 break;
nickjillings@1408 1524 case 'elementTracker':
nickjillings@1408 1525 this.enableElementTracker = true;
nickjillings@1408 1526 break;
nickjillings@1408 1527 case 'elementListenTracker':
nickjillings@1408 1528 this.enableElementListenTracker = true;
nickjillings@1408 1529 break;
nickjillings@1408 1530 case 'elementInitialPosition':
nickjillings@1408 1531 this.enableElementInitialPosition = true;
nickjillings@1408 1532 break;
nickjillings@1408 1533 case 'elementFlagListenedTo':
nickjillings@1408 1534 this.enableFlagListenedTo = true;
nickjillings@1408 1535 break;
nickjillings@1408 1536 case 'elementFlagMoved':
nickjillings@1408 1537 this.enableFlagMoved = true;
nickjillings@1408 1538 break;
nickjillings@1408 1539 case 'elementFlagComments':
nickjillings@1408 1540 this.enableFlagComments = true;
nickjillings@1408 1541 break;
nickjillings@1408 1542 }
nickjillings@1408 1543 }
nickjillings@1662 1544 this.initialiseTest = function(){};
nickjillings@1659 1545 }
nickjillings@1659 1546
nickjillings@1602 1547 function metricTracker(caller)
nickjillings@1659 1548 {
nickjillings@1659 1549 /* Custom object to track and collect metric data
nickjillings@1659 1550 * Used only inside the audioObjects object.
nickjillings@1659 1551 */
nickjillings@1659 1552
nickjillings@1659 1553 this.listenedTimer = 0;
nickjillings@1659 1554 this.listenStart = 0;
nickjillings@1617 1555 this.listenHold = false;
nickjillings@1661 1556 this.initialPosition = -1;
nickjillings@1659 1557 this.movementTracker = [];
nickjillings@2019 1558 this.listenTracker =[];
nickjillings@1659 1559 this.wasListenedTo = false;
nickjillings@1659 1560 this.wasMoved = false;
nickjillings@1659 1561 this.hasComments = false;
nickjillings@1602 1562 this.parent = caller;
nickjillings@1659 1563
nickjillings@1324 1564 this.initialise = function(position)
nickjillings@1659 1565 {
nickjillings@1661 1566 if (this.initialPosition == -1) {
nickjillings@1661 1567 this.initialPosition = position;
nickjillings@1325 1568 this.moved(0,position);
nickjillings@1661 1569 }
nickjillings@1659 1570 };
nickjillings@1659 1571
nickjillings@1659 1572 this.moved = function(time,position)
nickjillings@1659 1573 {
nickjillings@1325 1574 if (time > 0) {this.wasMoved = true;}
nickjillings@1659 1575 this.movementTracker[this.movementTracker.length] = [time, position];
nickjillings@1659 1576 };
nickjillings@1659 1577
nickjillings@1637 1578 this.startListening = function(time)
nickjillings@1659 1579 {
nickjillings@1617 1580 if (this.listenHold == false)
nickjillings@1659 1581 {
nickjillings@1659 1582 this.wasListenedTo = true;
nickjillings@1659 1583 this.listenStart = time;
nickjillings@1617 1584 this.listenHold = true;
nickjillings@2019 1585
nickjillings@2019 1586 var evnt = document.createElement('event');
nickjillings@2019 1587 var testTime = document.createElement('testTime');
nickjillings@2019 1588 testTime.setAttribute('start',time);
nickjillings@2019 1589 var bufferTime = document.createElement('bufferTime');
nickjillings@2019 1590 bufferTime.setAttribute('start',this.parent.getCurrentPosition());
nickjillings@2019 1591 evnt.appendChild(testTime);
nickjillings@2019 1592 evnt.appendChild(bufferTime);
nickjillings@2019 1593 this.listenTracker.push(evnt);
nickjillings@2019 1594
nickjillings@1602 1595 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
nickjillings@1602 1596 }
nickjillings@1602 1597 };
nickjillings@1637 1598
nickjillings@1566 1599 this.stopListening = function(time,bufferStopTime)
nickjillings@1637 1600 {
nickjillings@1637 1601 if (this.listenHold == true)
nickjillings@1637 1602 {
nickjillings@2019 1603 var diff = time - this.listenStart;
nickjillings@2019 1604 this.listenedTimer += (diff);
nickjillings@1659 1605 this.listenStart = 0;
nickjillings@1617 1606 this.listenHold = false;
nickjillings@2019 1607
nickjillings@2019 1608 var evnt = this.listenTracker[this.listenTracker.length-1];
nickjillings@2019 1609 var testTime = evnt.getElementsByTagName('testTime')[0];
nickjillings@2019 1610 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
nickjillings@2019 1611 testTime.setAttribute('stop',time);
nickjillings@1566 1612 if (bufferStopTime == undefined) {
nickjillings@1566 1613 bufferTime.setAttribute('stop',this.parent.getCurrentPosition());
nickjillings@1566 1614 } else {
nickjillings@1566 1615 bufferTime.setAttribute('stop',bufferStopTime);
nickjillings@1566 1616 }
nickjillings@2019 1617 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
nickjillings@1659 1618 }
nickjillings@1659 1619 };
nickjillings@1577 1620
nickjillings@1577 1621 this.exportXMLDOM = function() {
nickjillings@1324 1622 var storeDOM = [];
nickjillings@1577 1623 if (audioEngineContext.metric.enableElementTimer) {
nickjillings@1324 1624 var mElementTimer = storage.document.createElement('metricresult');
nickjillings@1577 1625 mElementTimer.setAttribute('name','enableElementTimer');
nickjillings@1577 1626 mElementTimer.textContent = this.listenedTimer;
nickjillings@1324 1627 storeDOM.push(mElementTimer);
nickjillings@1577 1628 }
nickjillings@1577 1629 if (audioEngineContext.metric.enableElementTracker) {
nickjillings@1324 1630 var elementTrackerFull = storage.document.createElement('metricResult');
nickjillings@1577 1631 elementTrackerFull.setAttribute('name','elementTrackerFull');
nickjillings@1577 1632 for (var k=0; k<this.movementTracker.length; k++)
nickjillings@1577 1633 {
nickjillings@2138 1634 var timePos = storage.document.createElement('movement');
nickjillings@2138 1635 timePos.setAttribute("time",this.movementTracker[k][0]);
nickjillings@2138 1636 timePos.setAttribute("value",this.movementTracker[k][1]);
nickjillings@1577 1637 elementTrackerFull.appendChild(timePos);
nickjillings@1577 1638 }
nickjillings@1324 1639 storeDOM.push(elementTrackerFull);
nickjillings@1577 1640 }
nickjillings@1577 1641 if (audioEngineContext.metric.enableElementListenTracker) {
nickjillings@1324 1642 var elementListenTracker = storage.document.createElement('metricResult');
nickjillings@1577 1643 elementListenTracker.setAttribute('name','elementListenTracker');
nickjillings@1577 1644 for (var k=0; k<this.listenTracker.length; k++) {
nickjillings@1577 1645 elementListenTracker.appendChild(this.listenTracker[k]);
nickjillings@1577 1646 }
nickjillings@1324 1647 storeDOM.push(elementListenTracker);
nickjillings@1577 1648 }
nickjillings@1577 1649 if (audioEngineContext.metric.enableElementInitialPosition) {
nickjillings@1324 1650 var elementInitial = storage.document.createElement('metricResult');
nickjillings@1577 1651 elementInitial.setAttribute('name','elementInitialPosition');
nickjillings@1577 1652 elementInitial.textContent = this.initialPosition;
nickjillings@1324 1653 storeDOM.push(elementInitial);
nickjillings@1577 1654 }
nickjillings@1577 1655 if (audioEngineContext.metric.enableFlagListenedTo) {
nickjillings@1324 1656 var flagListenedTo = storage.document.createElement('metricResult');
nickjillings@1577 1657 flagListenedTo.setAttribute('name','elementFlagListenedTo');
nickjillings@1577 1658 flagListenedTo.textContent = this.wasListenedTo;
nickjillings@1324 1659 storeDOM.push(flagListenedTo);
nickjillings@1577 1660 }
nickjillings@1577 1661 if (audioEngineContext.metric.enableFlagMoved) {
nickjillings@1324 1662 var flagMoved = storage.document.createElement('metricResult');
nickjillings@1577 1663 flagMoved.setAttribute('name','elementFlagMoved');
nickjillings@1577 1664 flagMoved.textContent = this.wasMoved;
nickjillings@1324 1665 storeDOM.push(flagMoved);
nickjillings@1577 1666 }
nickjillings@1577 1667 if (audioEngineContext.metric.enableFlagComments) {
nickjillings@1324 1668 var flagComments = storage.document.createElement('metricResult');
nickjillings@1577 1669 flagComments.setAttribute('name','elementFlagComments');
nickjillings@1577 1670 if (this.parent.commentDOM == null)
nickjillings@1577 1671 {flag.textContent = 'false';}
nickjillings@1577 1672 else if (this.parent.commentDOM.textContent.length == 0)
nickjillings@1577 1673 {flag.textContent = 'false';}
nickjillings@1577 1674 else
nickjillings@1577 1675 {flag.textContet = 'true';}
nickjillings@1324 1676 storeDOM.push(flagComments);
nickjillings@1577 1677 }
nickjillings@1324 1678 return storeDOM;
nickjillings@1577 1679 };
nickjillings@1664 1680 }
nickjillings@1664 1681
nickjillings@1664 1682 function randomiseOrder(input)
nickjillings@1664 1683 {
nickjillings@1664 1684 // This takes an array of information and randomises the order
nickjillings@1664 1685 var N = input.length;
b@2037 1686
b@2037 1687 var inputSequence = []; // For safety purposes: keep track of randomisation
b@2037 1688 for (var counter = 0; counter < N; ++counter)
b@2037 1689 inputSequence.push(counter) // Fill array
b@2037 1690 var inputSequenceClone = inputSequence.slice(0);
b@2037 1691
nickjillings@1664 1692 var holdArr = [];
b@2037 1693 var outputSequence = [];
nickjillings@1664 1694 for (var n=0; n<N; n++)
nickjillings@1664 1695 {
nickjillings@1664 1696 // First pick a random number
nickjillings@1664 1697 var r = Math.random();
nickjillings@1664 1698 // Multiply and floor by the number of elements left
nickjillings@1664 1699 r = Math.floor(r*input.length);
nickjillings@1664 1700 // Pick out that element and delete from the array
nickjillings@1664 1701 holdArr.push(input.splice(r,1)[0]);
b@2037 1702 // Do the same with sequence
b@2037 1703 outputSequence.push(inputSequence.splice(r,1)[0]);
nickjillings@1664 1704 }
b@2037 1705 console.log(inputSequenceClone.toString()); // print original array to console
b@2037 1706 console.log(outputSequence.toString()); // print randomised array to console
nickjillings@1664 1707 return holdArr;
nickjillings@1631 1708 }
nickjillings@1631 1709
nickjillings@1631 1710 function returnDateNode()
nickjillings@1631 1711 {
nickjillings@1631 1712 // Create an XML Node for the Date and Time a test was conducted
nickjillings@1631 1713 // Structure is
nickjillings@1631 1714 // <datetime>
nickjillings@1631 1715 // <date year="##" month="##" day="##">DD/MM/YY</date>
nickjillings@1631 1716 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
nickjillings@1631 1717 // </datetime>
nickjillings@1631 1718 var dateTime = new Date();
nickjillings@1631 1719 var year = document.createAttribute('year');
nickjillings@1631 1720 var month = document.createAttribute('month');
nickjillings@1631 1721 var day = document.createAttribute('day');
nickjillings@1631 1722 var hour = document.createAttribute('hour');
nickjillings@1631 1723 var minute = document.createAttribute('minute');
nickjillings@1631 1724 var secs = document.createAttribute('secs');
nickjillings@1631 1725
nickjillings@1631 1726 year.nodeValue = dateTime.getFullYear();
nickjillings@1631 1727 month.nodeValue = dateTime.getMonth()+1;
nickjillings@1631 1728 day.nodeValue = dateTime.getDate();
nickjillings@1631 1729 hour.nodeValue = dateTime.getHours();
nickjillings@1631 1730 minute.nodeValue = dateTime.getMinutes();
nickjillings@1631 1731 secs.nodeValue = dateTime.getSeconds();
nickjillings@1631 1732
nickjillings@1631 1733 var hold = document.createElement("datetime");
nickjillings@1631 1734 var date = document.createElement("date");
nickjillings@1631 1735 date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue;
nickjillings@1631 1736 var time = document.createElement("time");
nickjillings@1631 1737 time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue;
nickjillings@1631 1738
nickjillings@1631 1739 date.setAttributeNode(year);
nickjillings@1631 1740 date.setAttributeNode(month);
nickjillings@1631 1741 date.setAttributeNode(day);
nickjillings@1631 1742 time.setAttributeNode(hour);
nickjillings@1631 1743 time.setAttributeNode(minute);
nickjillings@1631 1744 time.setAttributeNode(secs);
nickjillings@1631 1745
nickjillings@1631 1746 hold.appendChild(date);
nickjillings@1631 1747 hold.appendChild(time);
nickjillings@1408 1748 return hold;
nickjillings@1631 1749
nickjillings@1604 1750 }
nickjillings@1604 1751
nickjillings@1580 1752 function Specification() {
nickjillings@1580 1753 // Handles the decoding of the project specification XML into a simple JavaScript Object.
nickjillings@1580 1754
nickjillings@1324 1755 this.interface = null;
nickjillings@1373 1756 this.projectReturn = "null";
nickjillings@1324 1757 this.randomiseOrder = null;
nickjillings@1324 1758 this.testPages = null;
nickjillings@1324 1759 this.pages = [];
nickjillings@1324 1760 this.metrics = null;
nickjillings@1324 1761 this.interfaces = null;
nickjillings@1324 1762 this.loudness = null;
nickjillings@1324 1763 this.errors = [];
nickjillings@1324 1764 this.schema = null;
nickjillings@1318 1765
nickjillings@1324 1766 this.processAttribute = function(attribute,schema)
nickjillings@1406 1767 {
nickjillings@1324 1768 // attribute is the string returned from getAttribute on the XML
nickjillings@1324 1769 // schema is the <xs:attribute> node
nickjillings@1324 1770 if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined)
nickjillings@1406 1771 {
nickjillings@1348 1772 schema = this.schema.getAllElementsByName(schema.getAttribute('ref'))[0];
nickjillings@1324 1773 }
nickjillings@1324 1774 var defaultOpt = schema.getAttribute('default');
nickjillings@1324 1775 if (attribute == null) {
nickjillings@1324 1776 attribute = defaultOpt;
nickjillings@1324 1777 }
nickjillings@1324 1778 var dataType = schema.getAttribute('type');
nickjillings@1324 1779 if (typeof dataType == "string") { dataType = dataType.substr(3);}
nickjillings@1324 1780 else {dataType = "string";}
nickjillings@1324 1781 if (attribute == null)
nickjillings@1324 1782 {
nickjillings@1324 1783 return attribute;
nickjillings@1324 1784 }
nickjillings@1324 1785 switch(dataType)
nickjillings@1324 1786 {
nickjillings@1324 1787 case "boolean":
nickjillings@1324 1788 if (attribute == 'true'){attribute = true;}else{attribute=false;}
nickjillings@1324 1789 break;
nickjillings@1324 1790 case "negativeInteger":
nickjillings@1324 1791 case "positiveInteger":
nickjillings@1324 1792 case "nonNegativeInteger":
nickjillings@1324 1793 case "nonPositiveInteger":
nickjillings@1324 1794 case "integer":
nickjillings@1324 1795 case "decimal":
nickjillings@1324 1796 case "short":
nickjillings@1324 1797 attribute = Number(attribute);
nickjillings@1324 1798 break;
nickjillings@1324 1799 case "string":
nickjillings@1324 1800 default:
nickjillings@1324 1801 attribute = String(attribute);
nickjillings@1324 1802 break;
nickjillings@1324 1803 }
nickjillings@1324 1804 return attribute;
nickjillings@1406 1805 };
nickjillings@1411 1806
nickjillings@1406 1807 this.decode = function(projectXML) {
nickjillings@1324 1808 this.errors = [];
nickjillings@1580 1809 // projectXML - DOM Parsed document
nickjillings@2052 1810 this.projectXML = projectXML.childNodes[0];
nickjillings@1580 1811 var setupNode = projectXML.getElementsByTagName('setup')[0];
nickjillings@1348 1812 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
nickjillings@1324 1813 // First decode the attributes
nickjillings@1348 1814 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
nickjillings@1324 1815 for (var i in attributes)
nickjillings@1520 1816 {
nickjillings@1324 1817 if (isNaN(Number(i)) == true){break;}
nickjillings@1324 1818 var attributeName = attributes[i].getAttribute('name');
nickjillings@1324 1819 var projectAttr = setupNode.getAttribute(attributeName);
nickjillings@1324 1820 projectAttr = this.processAttribute(projectAttr,attributes[i]);
nickjillings@1324 1821 switch(typeof projectAttr)
nickjillings@1432 1822 {
nickjillings@1324 1823 case "number":
nickjillings@1324 1824 case "boolean":
nickjillings@1324 1825 eval('this.'+attributeName+' = '+projectAttr);
nickjillings@1324 1826 break;
nickjillings@1324 1827 case "string":
nickjillings@1324 1828 eval('this.'+attributeName+' = "'+projectAttr+'"');
nickjillings@1324 1829 break;
nickjillings@1432 1830 }
nickjillings@1324 1831
nickjillings@1406 1832 }
nickjillings@1406 1833
nickjillings@1370 1834 this.metrics = new this.metricNode();
nickjillings@1580 1835
nickjillings@1324 1836 this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]);
nickjillings@1324 1837
nickjillings@1324 1838 // Now process the survey node options
nickjillings@1324 1839 var survey = setupNode.getElementsByTagName('survey');
nickjillings@1324 1840 for (var i in survey) {
nickjillings@1324 1841 if (isNaN(Number(i)) == true){break;}
nickjillings@1324 1842 var location = survey[i].getAttribute('location');
nickjillings@1324 1843 if (location == 'pre' || location == 'before')
nickjillings@1324 1844 {
nickjillings@1324 1845 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
nickjillings@1324 1846 else {
nickjillings@1324 1847 this.preTest = new this.surveyNode();
nickjillings@1370 1848 this.preTest.decode(this,survey[i]);
nickjillings@1324 1849 }
nickjillings@1324 1850 } else if (location == 'post' || location == 'after') {
nickjillings@1324 1851 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
nickjillings@1324 1852 else {
nickjillings@1324 1853 this.postTest = new this.surveyNode();
nickjillings@1370 1854 this.postTest.decode(this,survey[i]);
nickjillings@1324 1855 }
nickjillings@1580 1856 }
nickjillings@1580 1857 }
nickjillings@1580 1858
nickjillings@1324 1859 var interfaceNode = setupNode.getElementsByTagName('interface');
nickjillings@1324 1860 if (interfaceNode.length > 1)
nickjillings@1324 1861 {
nickjillings@1324 1862 this.errors.push("Only one <interface> node in the <setup> node allowed! Others except first ingnored!");
nickjillings@1324 1863 }
nickjillings@1324 1864 this.interfaces = new this.interfaceNode();
nickjillings@1324 1865 if (interfaceNode.length != 0)
nickjillings@1324 1866 {
nickjillings@1324 1867 interfaceNode = interfaceNode[0];
nickjillings@1348 1868 this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]);
nickjillings@1556 1869 }
nickjillings@1556 1870
nickjillings@1324 1871 // Page tags
nickjillings@1324 1872 var pageTags = projectXML.getElementsByTagName('page');
nickjillings@1348 1873 var pageSchema = this.schema.getAllElementsByName('page')[0];
nickjillings@1324 1874 for (var i=0; i<pageTags.length; i++)
nickjillings@1520 1875 {
nickjillings@1324 1876 var node = new this.page();
nickjillings@1324 1877 node.decode(this,pageTags[i],pageSchema);
nickjillings@1324 1878 this.pages.push(node);
nickjillings@1520 1879 }
nickjillings@1580 1880 };
nickjillings@1580 1881
nickjillings@1406 1882 this.encode = function()
nickjillings@1406 1883 {
nickjillings@1372 1884 var RootDocument = document.implementation.createDocument(null,"waet");
nickjillings@1372 1885 var root = RootDocument.children[0];
nickjillings@1372 1886 root.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
nickjillings@1372 1887 root.setAttribute("xsi:noNamespaceSchemaLocation","test-schema.xsd");
nickjillings@1324 1888 // Build setup node
nickjillings@1372 1889 var setup = RootDocument.createElement("setup");
nickjillings@1372 1890 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
nickjillings@1372 1891 // First decode the attributes
nickjillings@1372 1892 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
nickjillings@1372 1893 for (var i=0; i<attributes.length; i++)
nickjillings@1372 1894 {
nickjillings@1372 1895 var name = attributes[i].getAttribute("name");
nickjillings@1372 1896 if (name == undefined) {
nickjillings@1372 1897 name = attributes[i].getAttribute("ref");
nickjillings@1372 1898 }
nickjillings@1372 1899 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
nickjillings@1372 1900 {
nickjillings@1372 1901 eval("setup.setAttribute('"+name+"',this."+name+")");
nickjillings@1372 1902 }
nickjillings@1372 1903 }
nickjillings@1372 1904 root.appendChild(setup);
nickjillings@1372 1905 // Survey node
nickjillings@1372 1906 setup.appendChild(this.preTest.encode(RootDocument));
nickjillings@1372 1907 setup.appendChild(this.postTest.encode(RootDocument));
nickjillings@1372 1908 setup.appendChild(this.metrics.encode(RootDocument));
nickjillings@1372 1909 setup.appendChild(this.interfaces.encode(RootDocument));
nickjillings@1372 1910 for (var page of this.pages)
nickjillings@1372 1911 {
nickjillings@1372 1912 root.appendChild(page.encode(RootDocument));
nickjillings@1372 1913 }
nickjillings@1372 1914 return RootDocument;
nickjillings@1406 1915 };
nickjillings@1406 1916
nickjillings@1324 1917 this.surveyNode = function() {
nickjillings@1324 1918 this.location = null;
nickjillings@1580 1919 this.options = [];
nickjillings@1370 1920 this.schema = specification.schema.getAllElementsByName('survey')[0];
nickjillings@1580 1921
nickjillings@1406 1922 this.OptionNode = function() {
nickjillings@1406 1923 this.type = undefined;
nickjillings@1370 1924 this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
nickjillings@1406 1925 this.id = undefined;
nickjillings@1406 1926 this.mandatory = undefined;
nickjillings@1406 1927 this.statement = undefined;
nickjillings@1406 1928 this.boxsize = undefined;
nickjillings@1406 1929 this.options = [];
nickjillings@1406 1930 this.min = undefined;
nickjillings@1406 1931 this.max = undefined;
nickjillings@1406 1932 this.step = undefined;
nickjillings@1406 1933
nickjillings@1370 1934 this.decode = function(parent,child)
nickjillings@1406 1935 {
nickjillings@1370 1936 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
nickjillings@1324 1937 for (var i in attributeMap){
nickjillings@1324 1938 if(isNaN(Number(i)) == true){break;}
nickjillings@1324 1939 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
nickjillings@1324 1940 var projectAttr = child.getAttribute(attributeName);
nickjillings@1324 1941 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
nickjillings@1324 1942 switch(typeof projectAttr)
nickjillings@1324 1943 {
nickjillings@1324 1944 case "number":
nickjillings@1324 1945 case "boolean":
nickjillings@1324 1946 eval('this.'+attributeName+' = '+projectAttr);
nickjillings@1324 1947 break;
nickjillings@1324 1948 case "string":
nickjillings@1324 1949 eval('this.'+attributeName+' = "'+projectAttr+'"');
nickjillings@1324 1950 break;
nickjillings@1406 1951 }
nickjillings@1324 1952 }
nickjillings@1324 1953 this.statement = child.getElementsByTagName('statement')[0].textContent;
nickjillings@1324 1954 if (this.type == "checkbox" || this.type == "radio") {
nickjillings@1324 1955 var children = child.getElementsByTagName('option');
nickjillings@1324 1956 if (children.length == null) {
nickjillings@1406 1957 console.log('Malformed' +child.nodeName+ 'entry');
nickjillings@1406 1958 this.statement = 'Malformed' +child.nodeName+ 'entry';
nickjillings@1406 1959 this.type = 'statement';
nickjillings@1406 1960 } else {
nickjillings@1406 1961 this.options = [];
nickjillings@1324 1962 for (var i in children)
nickjillings@1324 1963 {
nickjillings@1324 1964 if (isNaN(Number(i))==true){break;}
nickjillings@1324 1965 this.options.push({
nickjillings@1324 1966 name: children[i].getAttribute('name'),
nickjillings@1324 1967 text: children[i].textContent
nickjillings@1324 1968 });
nickjillings@1406 1969 }
nickjillings@1406 1970 }
nickjillings@2030 1971 }
nickjillings@1406 1972 };
nickjillings@1406 1973
nickjillings@1372 1974 this.exportXML = function(doc)
nickjillings@1406 1975 {
nickjillings@2111 1976 var node = doc.createElement('surveyentry');
nickjillings@1324 1977 node.setAttribute('type',this.type);
nickjillings@1372 1978 var statement = doc.createElement('statement');
nickjillings@1324 1979 statement.textContent = this.statement;
nickjillings@1324 1980 node.appendChild(statement);
nickjillings@1406 1981 switch(this.type)
nickjillings@1406 1982 {
nickjillings@1406 1983 case "statement":
nickjillings@1406 1984 break;
nickjillings@1406 1985 case "question":
nickjillings@2111 1986 node.id = this.id;
nickjillings@2111 1987 if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
nickjillings@2111 1988 if (this.boxsize != undefined) {node.setAttribute("boxsize",this.boxsize);}
nickjillings@2111 1989 break;
nickjillings@2111 1990 case "number":
nickjillings@2111 1991 node.id = this.id;
nickjillings@2111 1992 if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
nickjillings@2111 1993 if (this.min != undefined) {node.setAttribute("min", this.min);}
nickjillings@2111 1994 if (this.max != undefined) {node.setAttribute("max", this.max);}
nickjillings@2111 1995 break;
nickjillings@1406 1996 case "checkbox":
nickjillings@1318 1997 case "radio":
nickjillings@1406 1998 node.id = this.id;
nickjillings@1406 1999 for (var i=0; i<this.options.length; i++)
nickjillings@1406 2000 {
nickjillings@1406 2001 var option = this.options[i];
nickjillings@1372 2002 var optionNode = doc.createElement("option");
nickjillings@1406 2003 optionNode.setAttribute("name",option.name);
nickjillings@1406 2004 optionNode.textContent = option.text;
nickjillings@1406 2005 node.appendChild(optionNode);
nickjillings@1406 2006 }
nickjillings@1406 2007 break;
nickjillings@1588 2008 }
nickjillings@1406 2009 return node;
nickjillings@1406 2010 };
nickjillings@1406 2011 };
nickjillings@1370 2012 this.decode = function(parent,xml) {
nickjillings@1324 2013 this.location = xml.getAttribute('location');
nickjillings@1324 2014 if (this.location == 'before'){this.location = 'pre';}
nickjillings@1324 2015 else if (this.location == 'after'){this.location = 'post';}
nickjillings@1324 2016 for (var i in xml.children)
nickjillings@1324 2017 {
nickjillings@1324 2018 if(isNaN(Number(i))==true){break;}
nickjillings@1406 2019 var node = new this.OptionNode();
nickjillings@1370 2020 node.decode(parent,xml.children[i]);
nickjillings@1406 2021 this.options.push(node);
nickjillings@1324 2022 }
nickjillings@1324 2023 };
nickjillings@1372 2024 this.encode = function(doc) {
nickjillings@1372 2025 var node = doc.createElement('survey');
nickjillings@1324 2026 node.setAttribute('location',this.location);
nickjillings@1324 2027 for (var i=0; i<this.options.length; i++)
nickjillings@1324 2028 {
nickjillings@1372 2029 node.appendChild(this.options[i].exportXML(doc));
nickjillings@1324 2030 }
nickjillings@1324 2031 return node;
nickjillings@1324 2032 };
nickjillings@1324 2033 };
nickjillings@1324 2034
nickjillings@1324 2035 this.interfaceNode = function()
nickjillings@1324 2036 {
nickjillings@1324 2037 this.title = null;
nickjillings@1324 2038 this.name = null;
nickjillings@1324 2039 this.options = [];
nickjillings@1324 2040 this.scales = [];
nickjillings@1370 2041 this.schema = specification.schema.getAllElementsByName('interface')[1];
nickjillings@1324 2042
nickjillings@1370 2043 this.decode = function(parent,xml) {
nickjillings@1324 2044 this.name = xml.getAttribute('name');
nickjillings@1324 2045 var titleNode = xml.getElementsByTagName('title');
nickjillings@1324 2046 if (titleNode.length == 1)
nickjillings@1324 2047 {
nickjillings@1324 2048 this.title = titleNode[0].textContent;
nickjillings@1324 2049 }
nickjillings@1324 2050 var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
nickjillings@1324 2051 // Extract interfaceoption node schema
nickjillings@1370 2052 var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
nickjillings@1348 2053 var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
nickjillings@1324 2054 for (var i=0; i<interfaceOptionNodes.length; i++)
nickjillings@1324 2055 {
nickjillings@1324 2056 var ioNode = interfaceOptionNodes[i];
nickjillings@1324 2057 var option = {};
nickjillings@1324 2058 for (var j=0; j<attributeMap.length; j++)
nickjillings@1324 2059 {
nickjillings@1324 2060 var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
nickjillings@1324 2061 var projectAttr = ioNode.getAttribute(attributeName);
nickjillings@1324 2062 projectAttr = parent.processAttribute(projectAttr,attributeMap[j]);
nickjillings@1324 2063 switch(typeof projectAttr)
nickjillings@1324 2064 {
nickjillings@1324 2065 case "number":
nickjillings@1324 2066 case "boolean":
nickjillings@1324 2067 eval('option.'+attributeName+' = '+projectAttr);
nickjillings@1324 2068 break;
nickjillings@1324 2069 case "string":
nickjillings@1324 2070 eval('option.'+attributeName+' = "'+projectAttr+'"');
nickjillings@1324 2071 break;
nickjillings@1324 2072 }
nickjillings@1324 2073 }
nickjillings@1324 2074 this.options.push(option);
nickjillings@1324 2075 }
nickjillings@1324 2076
nickjillings@1324 2077 // Now the scales nodes
nickjillings@1324 2078 var scaleParent = xml.getElementsByTagName('scales');
nickjillings@1324 2079 if (scaleParent.length == 1) {
nickjillings@1324 2080 scaleParent = scaleParent[0];
nickjillings@1324 2081 for (var i=0; i<scaleParent.children.length; i++) {
nickjillings@1324 2082 var child = scaleParent.children[i];
nickjillings@1324 2083 this.scales.push({
nickjillings@1324 2084 text: child.textContent,
nickjillings@1324 2085 position: Number(child.getAttribute('position'))
nickjillings@1324 2086 });
nickjillings@1406 2087 }
nickjillings@1580 2088 }
nickjillings@1580 2089 };
nickjillings@1324 2090
nickjillings@1372 2091 this.encode = function(doc) {
nickjillings@1372 2092 var node = doc.createElement("interface");
nickjillings@1372 2093 if (typeof name == "string")
nickjillings@1372 2094 node.setAttribute("name",this.name);
nickjillings@1372 2095 for (var option of this.options)
nickjillings@1372 2096 {
nickjillings@1372 2097 var child = doc.createElement("interfaceoption");
nickjillings@1372 2098 child.setAttribute("type",option.type);
nickjillings@1372 2099 child.setAttribute("name",option.name);
nickjillings@1372 2100 node.appendChild(child);
nickjillings@1372 2101 }
nickjillings@1372 2102 if (this.scales.length != 0) {
nickjillings@1372 2103 var scales = doc.createElement("scales");
nickjillings@1372 2104 for (var scale of this.scales)
nickjillings@1372 2105 {
nickjillings@1372 2106 var child = doc.createElement("scalelabel");
nickjillings@1372 2107 child.setAttribute("position",scale.position);
nickjillings@1372 2108 child.textContent = scale.text;
nickjillings@1372 2109 scales.appendChild(child);
nickjillings@1372 2110 }
nickjillings@1372 2111 node.appendChild(scales);
nickjillings@1372 2112 }
nickjillings@1372 2113 return node;
nickjillings@1324 2114 };
nickjillings@1580 2115 };
nickjillings@1580 2116
nickjillings@1370 2117 this.metricNode = function() {
nickjillings@1370 2118 this.enabled = [];
nickjillings@1370 2119 this.decode = function(parent, xml) {
nickjillings@1370 2120 var children = xml.getElementsByTagName('metricenable');
nickjillings@1370 2121 for (var i in children) {
nickjillings@1370 2122 if (isNaN(Number(i)) == true){break;}
nickjillings@1370 2123 this.enabled.push(children[i].textContent);
nickjillings@1370 2124 }
nickjillings@1370 2125 }
nickjillings@1372 2126 this.encode = function(doc) {
nickjillings@1372 2127 var node = doc.createElement('metric');
nickjillings@1370 2128 for (var i in this.enabled)
nickjillings@1370 2129 {
nickjillings@1370 2130 if (isNaN(Number(i)) == true){break;}
nickjillings@1372 2131 var child = doc.createElement('metricenable');
nickjillings@1370 2132 child.textContent = this.enabled[i];
nickjillings@1370 2133 node.appendChild(child);
nickjillings@1370 2134 }
nickjillings@1370 2135 return node;
nickjillings@1370 2136 }
nickjillings@1370 2137 }
nickjillings@1370 2138
nickjillings@1324 2139 this.page = function() {
nickjillings@1406 2140 this.presentedId = undefined;
nickjillings@1406 2141 this.id = undefined;
nickjillings@1406 2142 this.hostURL = undefined;
nickjillings@1406 2143 this.randomiseOrder = undefined;
nickjillings@1406 2144 this.loop = undefined;
nickjillings@1324 2145 this.showElementComments = undefined;
nickjillings@1406 2146 this.outsideReference = null;
nickjillings@1432 2147 this.loudness = null;
nickjillings@1324 2148 this.preTest = null;
nickjillings@1324 2149 this.postTest = null;
nickjillings@1406 2150 this.interfaces = [];
nickjillings@1406 2151 this.commentBoxPrefix = "Comment on track";
nickjillings@1406 2152 this.audioElements = [];
nickjillings@1406 2153 this.commentQuestions = [];
nickjillings@1370 2154 this.schema = specification.schema.getAllElementsByName("page")[0];
nickjillings@1406 2155
nickjillings@1406 2156 this.decode = function(parent,xml)
nickjillings@1406 2157 {
nickjillings@1348 2158 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
nickjillings@1324 2159 for (var i=0; i<attributeMap.length; i++)
nickjillings@1432 2160 {
nickjillings@1324 2161 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
nickjillings@1324 2162 var projectAttr = xml.getAttribute(attributeName);
nickjillings@1324 2163 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
nickjillings@1324 2164 switch(typeof projectAttr)
nickjillings@1395 2165 {
nickjillings@1324 2166 case "number":
nickjillings@1324 2167 case "boolean":
nickjillings@1324 2168 eval('this.'+attributeName+' = '+projectAttr);
nickjillings@1324 2169 break;
nickjillings@1324 2170 case "string":
nickjillings@1324 2171 eval('this.'+attributeName+' = "'+projectAttr+'"');
nickjillings@1324 2172 break;
nickjillings@1406 2173 }
nickjillings@1406 2174 }
nickjillings@1406 2175
nickjillings@1324 2176 // Get the Comment Box Prefix
nickjillings@1324 2177 var CBP = xml.getElementsByTagName('commentboxprefix');
nickjillings@1324 2178 if (CBP.length != 0) {
nickjillings@1324 2179 this.commentBoxPrefix = CBP[0].textContent;
nickjillings@1441 2180 }
nickjillings@1441 2181
nickjillings@1324 2182 // Now decode the interfaces
nickjillings@1324 2183 var interfaceNode = xml.getElementsByTagName('interface');
nickjillings@1324 2184 for (var i=0; i<interfaceNode.length; i++)
nickjillings@1324 2185 {
nickjillings@1324 2186 var node = new parent.interfaceNode();
nickjillings@1348 2187 node.decode(this,interfaceNode[i],parent.schema.getAllElementsByName('interface')[1]);
nickjillings@1324 2188 this.interfaces.push(node);
nickjillings@1324 2189 }
nickjillings@1411 2190
nickjillings@1324 2191 // Now process the survey node options
nickjillings@1324 2192 var survey = xml.getElementsByTagName('survey');
nickjillings@1348 2193 var surveySchema = parent.schema.getAllElementsByName('survey')[0];
nickjillings@1324 2194 for (var i in survey) {
nickjillings@1324 2195 if (isNaN(Number(i)) == true){break;}
nickjillings@1324 2196 var location = survey[i].getAttribute('location');
nickjillings@1324 2197 if (location == 'pre' || location == 'before')
nickjillings@1324 2198 {
nickjillings@1324 2199 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
nickjillings@1324 2200 else {
nickjillings@1324 2201 this.preTest = new parent.surveyNode();
nickjillings@1324 2202 this.preTest.decode(parent,survey[i],surveySchema);
nickjillings@1324 2203 }
nickjillings@1324 2204 } else if (location == 'post' || location == 'after') {
nickjillings@1324 2205 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
nickjillings@1324 2206 else {
nickjillings@1324 2207 this.postTest = new parent.surveyNode();
nickjillings@1324 2208 this.postTest.decode(parent,survey[i],surveySchema);
nickjillings@1324 2209 }
nickjillings@1324 2210 }
nickjillings@1324 2211 }
nickjillings@1324 2212
nickjillings@1324 2213 // Now process the audioelement tags
nickjillings@1324 2214 var audioElements = xml.getElementsByTagName('audioelement');
nickjillings@1324 2215 for (var i=0; i<audioElements.length; i++)
nickjillings@1324 2216 {
nickjillings@1324 2217 var node = new this.audioElementNode();
nickjillings@1370 2218 node.decode(this,audioElements[i]);
nickjillings@1324 2219 this.audioElements.push(node);
nickjillings@1324 2220 }
nickjillings@1324 2221
nickjillings@1324 2222 // Now decode the commentquestions
nickjillings@1324 2223 var commentQuestions = xml.getElementsByTagName('commentquestion');
nickjillings@1324 2224 for (var i=0; i<commentQuestions.length; i++)
nickjillings@1324 2225 {
nickjillings@1406 2226 var node = new this.commentQuestionNode();
nickjillings@1370 2227 node.decode(parent,commentQuestions[i]);
nickjillings@1406 2228 this.commentQuestions.push(node);
nickjillings@1580 2229 }
nickjillings@1580 2230 };
nickjillings@1580 2231
nickjillings@1406 2232 this.encode = function(root)
nickjillings@1406 2233 {
nickjillings@1372 2234 var AHNode = root.createElement("page");
nickjillings@1372 2235 // First decode the attributes
nickjillings@1372 2236 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
nickjillings@1372 2237 for (var i=0; i<attributes.length; i++)
nickjillings@1372 2238 {
nickjillings@1372 2239 var name = attributes[i].getAttribute("name");
nickjillings@1372 2240 if (name == undefined) {
nickjillings@1372 2241 name = attributes[i].getAttribute("ref");
nickjillings@1372 2242 }
nickjillings@1372 2243 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
nickjillings@1372 2244 {
nickjillings@1372 2245 eval("AHNode.setAttribute('"+name+"',this."+name+")");
nickjillings@1372 2246 }
nickjillings@1372 2247 }
nickjillings@1432 2248 if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
nickjillings@1372 2249 // <commentboxprefix>
nickjillings@1372 2250 var commentboxprefix = root.createElement("commentboxprefix");
nickjillings@1372 2251 commentboxprefix.textContent = this.commentBoxPrefix;
nickjillings@1372 2252 AHNode.appendChild(commentboxprefix);
nickjillings@1372 2253
nickjillings@1406 2254 for (var i=0; i<this.interfaces.length; i++)
nickjillings@1404 2255 {
nickjillings@1406 2256 AHNode.appendChild(this.interfaces[i].encode(root));
nickjillings@1406 2257 }
nickjillings@1558 2258
nickjillings@1406 2259 for (var i=0; i<this.audioElements.length; i++) {
nickjillings@1406 2260 AHNode.appendChild(this.audioElements[i].encode(root));
nickjillings@1406 2261 }
nickjillings@1406 2262 // Create <CommentQuestion>
nickjillings@1406 2263 for (var i=0; i<this.commentQuestions.length; i++)
nickjillings@1455 2264 {
nickjillings@1372 2265 AHNode.appendChild(this.commentQuestions[i].encode(root));
nickjillings@1406 2266 }
nickjillings@1406 2267
nickjillings@1372 2268 AHNode.appendChild(this.preTest.encode(root));
nickjillings@1372 2269 AHNode.appendChild(this.postTest.encode(root));
nickjillings@1406 2270 return AHNode;
nickjillings@1406 2271 };
nickjillings@1406 2272
nickjillings@1324 2273 this.commentQuestionNode = function() {
nickjillings@1324 2274 this.id = null;
nickjillings@1324 2275 this.type = undefined;
nickjillings@1406 2276 this.options = [];
nickjillings@1324 2277 this.statement = undefined;
nickjillings@1370 2278 this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
nickjillings@1370 2279 this.decode = function(parent,xml)
nickjillings@1406 2280 {
nickjillings@1324 2281 this.id = xml.id;
nickjillings@1324 2282 this.type = xml.getAttribute('type');
nickjillings@1324 2283 this.statement = xml.getElementsByTagName('statement')[0].textContent;
nickjillings@1324 2284 var optNodes = xml.getElementsByTagName('option');
nickjillings@1324 2285 for (var i=0; i<optNodes.length; i++)
nickjillings@1324 2286 {
nickjillings@1324 2287 var optNode = optNodes[i];
nickjillings@1324 2288 this.options.push({
nickjillings@1324 2289 name: optNode.getAttribute('name'),
nickjillings@1324 2290 text: optNode.textContent
nickjillings@1324 2291 });
nickjillings@1406 2292 }
nickjillings@1406 2293 };
nickjillings@1324 2294
nickjillings@1406 2295 this.encode = function(root)
nickjillings@1406 2296 {
nickjillings@1372 2297 var node = root.createElement("commentquestion");
nickjillings@1372 2298 node.id = this.id;
nickjillings@1372 2299 node.setAttribute("type",this.type);
nickjillings@1372 2300 var statement = root.createElement("statement");
nickjillings@1372 2301 statement.textContent = this.statement;
nickjillings@1372 2302 node.appendChild(statement);
nickjillings@1372 2303 for (var option of this.options)
nickjillings@1372 2304 {
nickjillings@1372 2305 var child = root.createElement("option");
nickjillings@1372 2306 child.setAttribute("name",option.name);
nickjillings@1372 2307 child.textContent = option.text;
nickjillings@1372 2308 node.appendChild(child);
nickjillings@1372 2309 }
nickjillings@1372 2310 return node;
nickjillings@1406 2311 };
nickjillings@1406 2312 };
nickjillings@1406 2313
nickjillings@1406 2314 this.audioElementNode = function() {
nickjillings@1406 2315 this.url = null;
nickjillings@1406 2316 this.id = null;
nickjillings@1406 2317 this.parent = null;
nickjillings@1324 2318 this.type = null;
nickjillings@1310 2319 this.marker = null;
nickjillings@1406 2320 this.enforce = false;
nickjillings@2130 2321 this.gain = 0.0;
nickjillings@1370 2322 this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
nickjillings@1324 2323 this.parent = null;
nickjillings@1406 2324 this.decode = function(parent,xml)
nickjillings@1406 2325 {
nickjillings@1406 2326 this.parent = parent;
nickjillings@1348 2327 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
nickjillings@1324 2328 for (var i=0; i<attributeMap.length; i++)
nickjillings@1426 2329 {
nickjillings@1324 2330 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
nickjillings@1324 2331 var projectAttr = xml.getAttribute(attributeName);
nickjillings@1324 2332 projectAttr = specification.processAttribute(projectAttr,attributeMap[i]);
nickjillings@1324 2333 switch(typeof projectAttr)
nickjillings@1406 2334 {
nickjillings@1324 2335 case "number":
nickjillings@1324 2336 case "boolean":
nickjillings@1324 2337 eval('this.'+attributeName+' = '+projectAttr);
nickjillings@1324 2338 break;
nickjillings@1324 2339 case "string":
nickjillings@1324 2340 eval('this.'+attributeName+' = "'+projectAttr+'"');
nickjillings@1324 2341 break;
nickjillings@1455 2342 }
nickjillings@1455 2343 }
nickjillings@1324 2344
nickjillings@1406 2345 };
nickjillings@1406 2346 this.encode = function(root)
nickjillings@1406 2347 {
nickjillings@1372 2348 var AENode = root.createElement("audioelement");
nickjillings@1372 2349 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
nickjillings@1372 2350 for (var i=0; i<attributes.length; i++)
nickjillings@1372 2351 {
nickjillings@1372 2352 var name = attributes[i].getAttribute("name");
nickjillings@1372 2353 if (name == undefined) {
nickjillings@1372 2354 name = attributes[i].getAttribute("ref");
nickjillings@1372 2355 }
nickjillings@1372 2356 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
nickjillings@1372 2357 {
nickjillings@1372 2358 eval("AENode.setAttribute('"+name+"',this."+name+")");
nickjillings@1372 2359 }
nickjillings@1372 2360 }
nickjillings@1406 2361 return AENode;
nickjillings@1406 2362 };
nickjillings@1580 2363 };
nickjillings@1580 2364 };
nickjillings@1580 2365 }
nickjillings@1406 2366
nickjillings@1582 2367 function Interface(specificationObject) {
nickjillings@1580 2368 // This handles the bindings between the interface and the audioEngineContext;
nickjillings@1582 2369 this.specification = specificationObject;
nickjillings@1582 2370 this.insertPoint = document.getElementById("topLevelBody");
nickjillings@1580 2371
nickjillings@1324 2372 this.newPage = function(audioHolderObject,store)
nickjillings@1407 2373 {
nickjillings@1369 2374 audioEngineContext.newTestPage(audioHolderObject,store);
nickjillings@2117 2375 interfaceContext.commentBoxes.deleteCommentBoxes();
nickjillings@1407 2376 interfaceContext.deleteCommentQuestions();
nickjillings@1324 2377 loadTest(audioHolderObject,store);
nickjillings@1407 2378 };
nickjillings@1407 2379
nickjillings@1582 2380 // Bounded by interface!!
nickjillings@1582 2381 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
nickjillings@1582 2382 // For example, APE returns the slider position normalised in a <value> tag.
nickjillings@1582 2383 this.interfaceObjects = [];
nickjillings@1582 2384 this.interfaceObject = function(){};
nickjillings@1582 2385
nickjillings@1525 2386 this.resizeWindow = function(event)
nickjillings@1525 2387 {
nickjillings@1421 2388 popup.resize(event);
nickjillings@1525 2389 for(var i=0; i<this.commentBoxes.length; i++)
nickjillings@1525 2390 {this.commentBoxes[i].resize();}
nickjillings@1525 2391 for(var i=0; i<this.commentQuestions.length; i++)
nickjillings@1525 2392 {this.commentQuestions[i].resize();}
nickjillings@1525 2393 try
nickjillings@1525 2394 {
nickjillings@1525 2395 resizeWindow(event);
nickjillings@1525 2396 }
nickjillings@1525 2397 catch(err)
nickjillings@1525 2398 {
nickjillings@1525 2399 console.log("Warning - Interface does not have Resize option");
nickjillings@1525 2400 console.log(err);
nickjillings@1525 2401 }
nickjillings@1525 2402 };
nickjillings@1525 2403
nickjillings@1465 2404 this.returnNavigator = function()
nickjillings@1465 2405 {
nickjillings@1362 2406 var node = storage.document.createElement("navigator");
nickjillings@1362 2407 var platform = storage.document.createElement("platform");
nickjillings@1465 2408 platform.textContent = navigator.platform;
nickjillings@1362 2409 var vendor = storage.document.createElement("vendor");
nickjillings@1465 2410 vendor.textContent = navigator.vendor;
nickjillings@1362 2411 var userAgent = storage.document.createElement("uagent");
nickjillings@1465 2412 userAgent.textContent = navigator.userAgent;
nickjillings@1362 2413 var screen = storage.document.createElement("window");
nickjillings@1362 2414 screen.setAttribute('innerWidth',window.innerWidth);
nickjillings@1362 2415 screen.setAttribute('innerHeight',window.innerHeight);
nickjillings@1465 2416 node.appendChild(platform);
nickjillings@1465 2417 node.appendChild(vendor);
nickjillings@1465 2418 node.appendChild(userAgent);
nickjillings@1362 2419 node.appendChild(screen);
nickjillings@1465 2420 return node;
nickjillings@1465 2421 };
nickjillings@1465 2422
nickjillings@2117 2423 this.commentBoxes = new function() {
nickjillings@2117 2424 this.boxes = [];
nickjillings@2117 2425 this.injectPoint = null;
nickjillings@2117 2426 this.elementCommentBox = function(audioObject) {
nickjillings@2117 2427 var element = audioObject.specification;
nickjillings@2117 2428 this.audioObject = audioObject;
nickjillings@2117 2429 this.id = audioObject.id;
nickjillings@2117 2430 var audioHolderObject = audioObject.specification.parent;
nickjillings@2117 2431 // Create document objects to hold the comment boxes
nickjillings@2117 2432 this.trackComment = document.createElement('div');
nickjillings@2117 2433 this.trackComment.className = 'comment-div';
nickjillings@2117 2434 this.trackComment.id = 'comment-div-'+audioObject.id;
nickjillings@2117 2435 // Create a string next to each comment asking for a comment
nickjillings@2117 2436 this.trackString = document.createElement('span');
nickjillings@2117 2437 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
nickjillings@2117 2438 // Create the HTML5 comment box 'textarea'
nickjillings@2117 2439 this.trackCommentBox = document.createElement('textarea');
nickjillings@2117 2440 this.trackCommentBox.rows = '4';
nickjillings@2117 2441 this.trackCommentBox.cols = '100';
nickjillings@2117 2442 this.trackCommentBox.name = 'trackComment'+audioObject.id;
nickjillings@2117 2443 this.trackCommentBox.className = 'trackComment';
nickjillings@2117 2444 var br = document.createElement('br');
nickjillings@2117 2445 // Add to the holder.
nickjillings@2117 2446 this.trackComment.appendChild(this.trackString);
nickjillings@2117 2447 this.trackComment.appendChild(br);
nickjillings@2117 2448 this.trackComment.appendChild(this.trackCommentBox);
nickjillings@2117 2449
nickjillings@2117 2450 this.exportXMLDOM = function() {
nickjillings@2117 2451 var root = document.createElement('comment');
nickjillings@2117 2452 var question = document.createElement('question');
nickjillings@2117 2453 question.textContent = this.trackString.textContent;
nickjillings@2117 2454 var response = document.createElement('response');
nickjillings@2117 2455 response.textContent = this.trackCommentBox.value;
nickjillings@2117 2456 console.log("Comment frag-"+this.id+": "+response.textContent);
nickjillings@2117 2457 root.appendChild(question);
nickjillings@2117 2458 root.appendChild(response);
nickjillings@2117 2459 return root;
nickjillings@2117 2460 };
nickjillings@2117 2461 this.resize = function()
nickjillings@2117 2462 {
nickjillings@2117 2463 var boxwidth = (window.innerWidth-100)/2;
nickjillings@2117 2464 if (boxwidth >= 600)
nickjillings@2117 2465 {
nickjillings@2117 2466 boxwidth = 600;
nickjillings@2117 2467 }
nickjillings@2117 2468 else if (boxwidth < 400)
nickjillings@2117 2469 {
nickjillings@2117 2470 boxwidth = 400;
nickjillings@2117 2471 }
nickjillings@2117 2472 this.trackComment.style.width = boxwidth+"px";
nickjillings@2117 2473 this.trackCommentBox.style.width = boxwidth-6+"px";
nickjillings@2117 2474 };
nickjillings@2117 2475 this.resize();
nickjillings@2117 2476 };
nickjillings@2117 2477 this.createCommentBox = function(audioObject) {
nickjillings@2117 2478 var node = new this.elementCommentBox(audioObject);
nickjillings@2117 2479 this.boxes.push(node);
nickjillings@2117 2480 audioObject.commentDOM = node;
nickjillings@2117 2481 return node;
nickjillings@2117 2482 };
nickjillings@2117 2483 this.sortCommentBoxes = function() {
nickjillings@2117 2484 this.boxes.sort(function(a,b){return a.id - b.id;});
nickjillings@2117 2485 };
nickjillings@2117 2486
nickjillings@2117 2487 this.showCommentBoxes = function(inject, sort) {
nickjillings@2117 2488 this.injectPoint = inject;
nickjillings@2117 2489 if (sort) {this.sortCommentBoxes();}
nickjillings@2117 2490 for (var box of this.boxes) {
nickjillings@2117 2491 inject.appendChild(box.trackComment);
nickjillings@2117 2492 }
nickjillings@2117 2493 };
nickjillings@2117 2494
nickjillings@2117 2495 this.deleteCommentBoxes = function() {
nickjillings@2117 2496 if (this.injectPoint != null) {
nickjillings@2117 2497 for (var box of this.boxes) {
nickjillings@2117 2498 this.injectPoint.removeChild(box.trackComment);
nickjillings@2117 2499 }
nickjillings@2117 2500 this.injectPoint = null;
nickjillings@2117 2501 }
nickjillings@2117 2502 this.boxes = [];
nickjillings@2117 2503 };
nickjillings@2117 2504 }
nickjillings@1582 2505
nickjillings@2032 2506 this.commentQuestions = [];
nickjillings@2032 2507
nickjillings@2032 2508 this.commentBox = function(commentQuestion) {
nickjillings@2032 2509 this.specification = commentQuestion;
nickjillings@2032 2510 // Create document objects to hold the comment boxes
nickjillings@2032 2511 this.holder = document.createElement('div');
nickjillings@2032 2512 this.holder.className = 'comment-div';
nickjillings@2032 2513 // Create a string next to each comment asking for a comment
nickjillings@2032 2514 this.string = document.createElement('span');
nickjillings@1324 2515 this.string.innerHTML = commentQuestion.statement;
nickjillings@2032 2516 // Create the HTML5 comment box 'textarea'
nickjillings@2032 2517 this.textArea = document.createElement('textarea');
nickjillings@2032 2518 this.textArea.rows = '4';
nickjillings@2032 2519 this.textArea.cols = '100';
nickjillings@2032 2520 this.textArea.className = 'trackComment';
nickjillings@2032 2521 var br = document.createElement('br');
nickjillings@2032 2522 // Add to the holder.
nickjillings@2032 2523 this.holder.appendChild(this.string);
nickjillings@2032 2524 this.holder.appendChild(br);
nickjillings@2032 2525 this.holder.appendChild(this.textArea);
nickjillings@2032 2526
nickjillings@2097 2527 this.exportXMLDOM = function(storePoint) {
nickjillings@2097 2528 var root = storePoint.parent.document.createElement('comment');
nickjillings@2032 2529 root.id = this.specification.id;
nickjillings@2032 2530 root.setAttribute('type',this.specification.type);
b@2059 2531 console.log("Question: "+this.string.textContent);
b@2059 2532 console.log("Response: "+root.textContent);
nickjillings@2097 2533 var question = storePoint.parent.document.createElement('question');
nickjillings@2097 2534 question.textContent = this.string.textContent;
nickjillings@2097 2535 var response = storePoint.parent.document.createElement('response');
nickjillings@2097 2536 response.textContent = this.textArea.value;
nickjillings@2097 2537 root.appendChild(question);
nickjillings@2097 2538 root.appendChild(response);
nickjillings@2097 2539 storePoint.XMLDOM.appendChild(root);
nickjillings@2032 2540 return root;
nickjillings@2032 2541 };
nickjillings@1525 2542 this.resize = function()
nickjillings@1525 2543 {
nickjillings@1525 2544 var boxwidth = (window.innerWidth-100)/2;
nickjillings@1525 2545 if (boxwidth >= 600)
nickjillings@1525 2546 {
nickjillings@1525 2547 boxwidth = 600;
nickjillings@1525 2548 }
nickjillings@1525 2549 else if (boxwidth < 400)
nickjillings@1525 2550 {
nickjillings@1525 2551 boxwidth = 400;
nickjillings@1525 2552 }
nickjillings@1525 2553 this.holder.style.width = boxwidth+"px";
nickjillings@1525 2554 this.textArea.style.width = boxwidth-6+"px";
nickjillings@1525 2555 };
nickjillings@1525 2556 this.resize();
nickjillings@2032 2557 };
nickjillings@2032 2558
nickjillings@2032 2559 this.radioBox = function(commentQuestion) {
nickjillings@2032 2560 this.specification = commentQuestion;
nickjillings@2032 2561 // Create document objects to hold the comment boxes
nickjillings@2032 2562 this.holder = document.createElement('div');
nickjillings@2032 2563 this.holder.className = 'comment-div';
nickjillings@2032 2564 // Create a string next to each comment asking for a comment
nickjillings@2032 2565 this.string = document.createElement('span');
nickjillings@2032 2566 this.string.innerHTML = commentQuestion.statement;
nickjillings@2032 2567 var br = document.createElement('br');
nickjillings@2032 2568 // Add to the holder.
nickjillings@2032 2569 this.holder.appendChild(this.string);
nickjillings@2032 2570 this.holder.appendChild(br);
nickjillings@2032 2571 this.options = [];
nickjillings@2032 2572 this.inputs = document.createElement('div');
nickjillings@2032 2573 this.span = document.createElement('div');
nickjillings@2032 2574 this.inputs.align = 'center';
nickjillings@2032 2575 this.inputs.style.marginLeft = '12px';
nickjillings@2032 2576 this.span.style.marginLeft = '12px';
nickjillings@2032 2577 this.span.align = 'center';
nickjillings@2032 2578 this.span.style.marginTop = '15px';
nickjillings@2032 2579
nickjillings@2032 2580 var optCount = commentQuestion.options.length;
nickjillings@1324 2581 for (var optNode of commentQuestion.options)
nickjillings@2032 2582 {
nickjillings@2032 2583 var div = document.createElement('div');
nickjillings@1524 2584 div.style.width = '80px';
nickjillings@2032 2585 div.style.float = 'left';
nickjillings@2032 2586 var input = document.createElement('input');
nickjillings@2032 2587 input.type = 'radio';
nickjillings@2032 2588 input.name = commentQuestion.id;
nickjillings@1324 2589 input.setAttribute('setvalue',optNode.name);
nickjillings@2032 2590 input.className = 'comment-radio';
nickjillings@2032 2591 div.appendChild(input);
nickjillings@2032 2592 this.inputs.appendChild(div);
nickjillings@2032 2593
nickjillings@2032 2594
nickjillings@2032 2595 div = document.createElement('div');
nickjillings@1524 2596 div.style.width = '80px';
nickjillings@2032 2597 div.style.float = 'left';
nickjillings@2032 2598 div.align = 'center';
nickjillings@2032 2599 var span = document.createElement('span');
nickjillings@1324 2600 span.textContent = optNode.text;
nickjillings@2032 2601 span.className = 'comment-radio-span';
nickjillings@2032 2602 div.appendChild(span);
nickjillings@2032 2603 this.span.appendChild(div);
nickjillings@2032 2604 this.options.push(input);
nickjillings@2032 2605 }
nickjillings@2032 2606 this.holder.appendChild(this.span);
nickjillings@2032 2607 this.holder.appendChild(this.inputs);
nickjillings@2032 2608
nickjillings@2097 2609 this.exportXMLDOM = function(storePoint) {
nickjillings@2097 2610 var root = storePoint.parent.document.createElement('comment');
nickjillings@2032 2611 root.id = this.specification.id;
nickjillings@2032 2612 root.setAttribute('type',this.specification.type);
nickjillings@2032 2613 var question = document.createElement('question');
nickjillings@2032 2614 question.textContent = this.string.textContent;
nickjillings@2032 2615 var response = document.createElement('response');
nickjillings@2032 2616 var i=0;
nickjillings@2032 2617 while(this.options[i].checked == false) {
nickjillings@2032 2618 i++;
nickjillings@2032 2619 if (i >= this.options.length) {
nickjillings@2032 2620 break;
nickjillings@2032 2621 }
nickjillings@2032 2622 }
nickjillings@2032 2623 if (i >= this.options.length) {
nickjillings@2032 2624 response.textContent = 'null';
nickjillings@2032 2625 } else {
nickjillings@2032 2626 response.textContent = this.options[i].getAttribute('setvalue');
nickjillings@2032 2627 response.setAttribute('number',i);
nickjillings@2032 2628 }
nickjillings@1572 2629 console.log('Comment: '+question.textContent);
nickjillings@1572 2630 console.log('Response: '+response.textContent);
nickjillings@2032 2631 root.appendChild(question);
nickjillings@2032 2632 root.appendChild(response);
nickjillings@2097 2633 storePoint.XMLDOM.appendChild(root);
nickjillings@2032 2634 return root;
nickjillings@2032 2635 };
nickjillings@1525 2636 this.resize = function()
nickjillings@1525 2637 {
nickjillings@1525 2638 var boxwidth = (window.innerWidth-100)/2;
nickjillings@1525 2639 if (boxwidth >= 600)
nickjillings@1525 2640 {
nickjillings@1525 2641 boxwidth = 600;
nickjillings@1525 2642 }
nickjillings@1525 2643 else if (boxwidth < 400)
nickjillings@1525 2644 {
nickjillings@1525 2645 boxwidth = 400;
nickjillings@1525 2646 }
nickjillings@1525 2647 this.holder.style.width = boxwidth+"px";
nickjillings@1525 2648 var text = this.holder.children[2];
nickjillings@1525 2649 var options = this.holder.children[3];
nickjillings@1525 2650 var optCount = options.children.length;
nickjillings@1525 2651 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
nickjillings@1525 2652 var options = options.firstChild;
nickjillings@1525 2653 var text = text.firstChild;
nickjillings@1525 2654 options.style.marginRight = spanMargin;
nickjillings@1525 2655 options.style.marginLeft = spanMargin;
nickjillings@1525 2656 text.style.marginRight = spanMargin;
nickjillings@1525 2657 text.style.marginLeft = spanMargin;
nickjillings@1525 2658 while(options.nextSibling != undefined)
nickjillings@1525 2659 {
nickjillings@1525 2660 options = options.nextSibling;
nickjillings@1525 2661 text = text.nextSibling;
nickjillings@1525 2662 options.style.marginRight = spanMargin;
nickjillings@1525 2663 options.style.marginLeft = spanMargin;
nickjillings@1525 2664 text.style.marginRight = spanMargin;
nickjillings@1525 2665 text.style.marginLeft = spanMargin;
nickjillings@1525 2666 }
nickjillings@1525 2667 };
nickjillings@1525 2668 this.resize();
nickjillings@2032 2669 };
nickjillings@2032 2670
nickjillings@1572 2671 this.checkboxBox = function(commentQuestion) {
nickjillings@1572 2672 this.specification = commentQuestion;
nickjillings@1572 2673 // Create document objects to hold the comment boxes
nickjillings@1572 2674 this.holder = document.createElement('div');
nickjillings@1572 2675 this.holder.className = 'comment-div';
nickjillings@1572 2676 // Create a string next to each comment asking for a comment
nickjillings@1572 2677 this.string = document.createElement('span');
nickjillings@1572 2678 this.string.innerHTML = commentQuestion.statement;
nickjillings@1572 2679 var br = document.createElement('br');
nickjillings@1572 2680 // Add to the holder.
nickjillings@1572 2681 this.holder.appendChild(this.string);
nickjillings@1572 2682 this.holder.appendChild(br);
nickjillings@1572 2683 this.options = [];
nickjillings@1572 2684 this.inputs = document.createElement('div');
nickjillings@1572 2685 this.span = document.createElement('div');
nickjillings@1572 2686 this.inputs.align = 'center';
nickjillings@1572 2687 this.inputs.style.marginLeft = '12px';
nickjillings@1572 2688 this.span.style.marginLeft = '12px';
nickjillings@1572 2689 this.span.align = 'center';
nickjillings@1572 2690 this.span.style.marginTop = '15px';
nickjillings@1572 2691
nickjillings@1572 2692 var optCount = commentQuestion.options.length;
nickjillings@1572 2693 for (var i=0; i<optCount; i++)
nickjillings@1572 2694 {
nickjillings@1572 2695 var div = document.createElement('div');
nickjillings@1524 2696 div.style.width = '80px';
nickjillings@1572 2697 div.style.float = 'left';
nickjillings@1572 2698 var input = document.createElement('input');
nickjillings@1572 2699 input.type = 'checkbox';
nickjillings@1572 2700 input.name = commentQuestion.id;
nickjillings@1572 2701 input.setAttribute('setvalue',commentQuestion.options[i].name);
nickjillings@1572 2702 input.className = 'comment-radio';
nickjillings@1572 2703 div.appendChild(input);
nickjillings@1572 2704 this.inputs.appendChild(div);
nickjillings@1572 2705
nickjillings@1572 2706
nickjillings@1572 2707 div = document.createElement('div');
nickjillings@1524 2708 div.style.width = '80px';
nickjillings@1572 2709 div.style.float = 'left';
nickjillings@1572 2710 div.align = 'center';
nickjillings@1572 2711 var span = document.createElement('span');
nickjillings@1572 2712 span.textContent = commentQuestion.options[i].text;
nickjillings@1572 2713 span.className = 'comment-radio-span';
nickjillings@1572 2714 div.appendChild(span);
nickjillings@1572 2715 this.span.appendChild(div);
nickjillings@1572 2716 this.options.push(input);
nickjillings@1572 2717 }
nickjillings@1572 2718 this.holder.appendChild(this.span);
nickjillings@1572 2719 this.holder.appendChild(this.inputs);
nickjillings@1572 2720
nickjillings@2097 2721 this.exportXMLDOM = function(storePoint) {
nickjillings@2097 2722 var root = storePoint.parent.document.createElement('comment');
nickjillings@1572 2723 root.id = this.specification.id;
nickjillings@1572 2724 root.setAttribute('type',this.specification.type);
nickjillings@1572 2725 var question = document.createElement('question');
nickjillings@1572 2726 question.textContent = this.string.textContent;
nickjillings@1572 2727 root.appendChild(question);
nickjillings@1572 2728 console.log('Comment: '+question.textContent);
nickjillings@1572 2729 for (var i=0; i<this.options.length; i++) {
nickjillings@1572 2730 var response = document.createElement('response');
nickjillings@1572 2731 response.textContent = this.options[i].checked;
nickjillings@1572 2732 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
nickjillings@1572 2733 root.appendChild(response);
nickjillings@1572 2734 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
nickjillings@1572 2735 }
nickjillings@2097 2736 storePoint.XMLDOM.appendChild(root);
nickjillings@1572 2737 return root;
nickjillings@1572 2738 };
nickjillings@1525 2739 this.resize = function()
nickjillings@1525 2740 {
nickjillings@1525 2741 var boxwidth = (window.innerWidth-100)/2;
nickjillings@1525 2742 if (boxwidth >= 600)
nickjillings@1525 2743 {
nickjillings@1525 2744 boxwidth = 600;
nickjillings@1525 2745 }
nickjillings@1525 2746 else if (boxwidth < 400)
nickjillings@1525 2747 {
nickjillings@1525 2748 boxwidth = 400;
nickjillings@1525 2749 }
nickjillings@1525 2750 this.holder.style.width = boxwidth+"px";
nickjillings@1525 2751 var text = this.holder.children[2];
nickjillings@1525 2752 var options = this.holder.children[3];
nickjillings@1525 2753 var optCount = options.children.length;
nickjillings@1525 2754 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
nickjillings@1525 2755 var options = options.firstChild;
nickjillings@1525 2756 var text = text.firstChild;
nickjillings@1525 2757 options.style.marginRight = spanMargin;
nickjillings@1525 2758 options.style.marginLeft = spanMargin;
nickjillings@1525 2759 text.style.marginRight = spanMargin;
nickjillings@1525 2760 text.style.marginLeft = spanMargin;
nickjillings@1525 2761 while(options.nextSibling != undefined)
nickjillings@1525 2762 {
nickjillings@1525 2763 options = options.nextSibling;
nickjillings@1525 2764 text = text.nextSibling;
nickjillings@1525 2765 options.style.marginRight = spanMargin;
nickjillings@1525 2766 options.style.marginLeft = spanMargin;
nickjillings@1525 2767 text.style.marginRight = spanMargin;
nickjillings@1525 2768 text.style.marginLeft = spanMargin;
nickjillings@1525 2769 }
nickjillings@1525 2770 };
nickjillings@1525 2771 this.resize();
nickjillings@1572 2772 };
nickjillings@1570 2773
nickjillings@2032 2774 this.createCommentQuestion = function(element) {
nickjillings@2032 2775 var node;
nickjillings@1324 2776 if (element.type == 'question') {
nickjillings@2032 2777 node = new this.commentBox(element);
nickjillings@2032 2778 } else if (element.type == 'radio') {
nickjillings@2032 2779 node = new this.radioBox(element);
nickjillings@1572 2780 } else if (element.type == 'checkbox') {
nickjillings@1572 2781 node = new this.checkboxBox(element);
nickjillings@2032 2782 }
nickjillings@2032 2783 this.commentQuestions.push(node);
nickjillings@2032 2784 return node;
nickjillings@2032 2785 };
nickjillings@1564 2786
nickjillings@2051 2787 this.deleteCommentQuestions = function()
nickjillings@2051 2788 {
nickjillings@2051 2789 this.commentQuestions = [];
nickjillings@2051 2790 };
nickjillings@2051 2791
nickjillings@1564 2792 this.playhead = new function()
nickjillings@1564 2793 {
nickjillings@1564 2794 this.object = document.createElement('div');
nickjillings@1564 2795 this.object.className = 'playhead';
nickjillings@1564 2796 this.object.align = 'left';
nickjillings@1564 2797 var curTime = document.createElement('div');
nickjillings@1564 2798 curTime.style.width = '50px';
nickjillings@1564 2799 this.curTimeSpan = document.createElement('span');
nickjillings@1564 2800 this.curTimeSpan.textContent = '00:00';
nickjillings@1564 2801 curTime.appendChild(this.curTimeSpan);
nickjillings@1564 2802 this.object.appendChild(curTime);
nickjillings@1564 2803 this.scrubberTrack = document.createElement('div');
nickjillings@1564 2804 this.scrubberTrack.className = 'playhead-scrub-track';
nickjillings@1564 2805
nickjillings@1564 2806 this.scrubberHead = document.createElement('div');
nickjillings@1564 2807 this.scrubberHead.id = 'playhead-scrubber';
nickjillings@1564 2808 this.scrubberTrack.appendChild(this.scrubberHead);
nickjillings@1564 2809 this.object.appendChild(this.scrubberTrack);
nickjillings@1564 2810
nickjillings@1564 2811 this.timePerPixel = 0;
nickjillings@1564 2812 this.maxTime = 0;
nickjillings@1564 2813
nickjillings@1567 2814 this.playbackObject;
nickjillings@1567 2815
nickjillings@1567 2816 this.setTimePerPixel = function(audioObject) {
nickjillings@1564 2817 //maxTime must be in seconds
nickjillings@1567 2818 this.playbackObject = audioObject;
nickjillings@1410 2819 this.maxTime = audioObject.buffer.buffer.duration;
nickjillings@1564 2820 var width = 490; //500 - 10, 5 each side of the tracker head
nickjillings@1567 2821 this.timePerPixel = this.maxTime/490;
nickjillings@1567 2822 if (this.maxTime < 60) {
nickjillings@1564 2823 this.curTimeSpan.textContent = '0.00';
nickjillings@1564 2824 } else {
nickjillings@1564 2825 this.curTimeSpan.textContent = '00:00';
nickjillings@1564 2826 }
nickjillings@1564 2827 };
nickjillings@1564 2828
nickjillings@1567 2829 this.update = function() {
nickjillings@1564 2830 // Update the playhead position, startPlay must be called
nickjillings@1564 2831 if (this.timePerPixel > 0) {
nickjillings@1567 2832 var time = this.playbackObject.getCurrentPosition();
nickjillings@1367 2833 if (time > 0 && time < this.maxTime) {
nickjillings@1530 2834 var width = 490;
nickjillings@1530 2835 var pix = Math.floor(time/this.timePerPixel);
nickjillings@1530 2836 this.scrubberHead.style.left = pix+'px';
nickjillings@1530 2837 if (this.maxTime > 60.0) {
nickjillings@1530 2838 var secs = time%60;
nickjillings@1530 2839 var mins = Math.floor((time-secs)/60);
nickjillings@1530 2840 secs = secs.toString();
nickjillings@1530 2841 secs = secs.substr(0,2);
nickjillings@1530 2842 mins = mins.toString();
nickjillings@1530 2843 this.curTimeSpan.textContent = mins+':'+secs;
nickjillings@1530 2844 } else {
nickjillings@1530 2845 time = time.toString();
nickjillings@1530 2846 this.curTimeSpan.textContent = time.substr(0,4);
nickjillings@1530 2847 }
nickjillings@1564 2848 } else {
nickjillings@1530 2849 this.scrubberHead.style.left = '0px';
nickjillings@1530 2850 if (this.maxTime < 60) {
nickjillings@1530 2851 this.curTimeSpan.textContent = '0.00';
nickjillings@1530 2852 } else {
nickjillings@1530 2853 this.curTimeSpan.textContent = '00:00';
nickjillings@1530 2854 }
nickjillings@1564 2855 }
nickjillings@1564 2856 }
nickjillings@1564 2857 };
nickjillings@1567 2858
nickjillings@1567 2859 this.interval = undefined;
nickjillings@1567 2860
nickjillings@1567 2861 this.start = function() {
nickjillings@1567 2862 if (this.playbackObject != undefined && this.interval == undefined) {
nickjillings@1530 2863 if (this.maxTime < 60) {
nickjillings@1530 2864 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
nickjillings@1530 2865 } else {
nickjillings@1530 2866 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
nickjillings@1530 2867 }
nickjillings@1567 2868 }
nickjillings@1567 2869 };
nickjillings@1567 2870 this.stop = function() {
nickjillings@1567 2871 clearInterval(this.interval);
nickjillings@1567 2872 this.interval = undefined;
nickjillings@2101 2873 this.scrubberHead.style.left = '0px';
nickjillings@1530 2874 if (this.maxTime < 60) {
nickjillings@1530 2875 this.curTimeSpan.textContent = '0.00';
nickjillings@1530 2876 } else {
nickjillings@1530 2877 this.curTimeSpan.textContent = '00:00';
nickjillings@1530 2878 }
nickjillings@1567 2879 };
nickjillings@1564 2880 };
nickjillings@1354 2881
nickjillings@1354 2882 this.volume = new function()
nickjillings@1354 2883 {
nickjillings@1354 2884 // An in-built volume module which can be viewed on page
nickjillings@1354 2885 // Includes trackers on page-by-page data
nickjillings@1354 2886 // Volume does NOT reset to 0dB on each page load
nickjillings@1354 2887 this.valueLin = 1.0;
nickjillings@1354 2888 this.valueDB = 0.0;
nickjillings@1354 2889 this.object = document.createElement('div');
nickjillings@1354 2890 this.object.id = 'master-volume-holder';
nickjillings@1354 2891 this.slider = document.createElement('input');
nickjillings@1354 2892 this.slider.id = 'master-volume-control';
nickjillings@1354 2893 this.slider.type = 'range';
nickjillings@1354 2894 this.valueText = document.createElement('span');
nickjillings@1354 2895 this.valueText.id = 'master-volume-feedback';
nickjillings@1354 2896 this.valueText.textContent = '0dB';
nickjillings@1354 2897
nickjillings@1354 2898 this.slider.min = -60;
nickjillings@1354 2899 this.slider.max = 12;
nickjillings@1354 2900 this.slider.value = 0;
nickjillings@1354 2901 this.slider.step = 1;
nickjillings@1354 2902 this.slider.onmousemove = function(event)
nickjillings@1354 2903 {
nickjillings@1354 2904 interfaceContext.volume.valueDB = event.currentTarget.value;
nickjillings@1354 2905 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
nickjillings@1354 2906 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
nickjillings@1354 2907 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
nickjillings@1354 2908 }
nickjillings@1354 2909 this.slider.onmouseup = function(event)
nickjillings@1354 2910 {
nickjillings@2100 2911 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
nickjillings@1354 2912 if (storePoint.length == 0)
nickjillings@1354 2913 {
nickjillings@1354 2914 storePoint = storage.document.createElement('metricresult');
nickjillings@1354 2915 storePoint.setAttribute('name','volumeTracker');
nickjillings@2100 2916 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
nickjillings@1354 2917 }
nickjillings@1354 2918 else {
nickjillings@1354 2919 storePoint = storePoint[0];
nickjillings@1354 2920 }
nickjillings@1354 2921 var node = storage.document.createElement('movement');
nickjillings@1354 2922 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
nickjillings@1354 2923 node.setAttribute('volume',interfaceContext.volume.valueDB);
nickjillings@1354 2924 node.setAttribute('format','dBFS');
nickjillings@1354 2925 storePoint.appendChild(node);
nickjillings@1354 2926 }
nickjillings@1354 2927
nickjillings@1355 2928 var title = document.createElement('div');
nickjillings@1355 2929 title.innerHTML = '<span>Master Volume Control</span>';
nickjillings@1355 2930 title.style.fontSize = '0.75em';
nickjillings@1355 2931 title.style.width = "100%";
nickjillings@1355 2932 title.align = 'center';
nickjillings@1355 2933 this.object.appendChild(title);
nickjillings@1355 2934
nickjillings@1354 2935 this.object.appendChild(this.slider);
nickjillings@1354 2936 this.object.appendChild(this.valueText);
nickjillings@1354 2937 }
nickjillings@2049 2938 // Global Checkers
nickjillings@2049 2939 // These functions will help enforce the checkers
nickjillings@2049 2940 this.checkHiddenAnchor = function()
nickjillings@2049 2941 {
nickjillings@1324 2942 for (var ao of audioEngineContext.audioObjects)
nickjillings@2049 2943 {
nickjillings@1324 2944 if (ao.specification.type == "anchor")
nickjillings@2049 2945 {
nickjillings@1325 2946 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
nickjillings@1324 2947 // Anchor is not set below
nickjillings@1324 2948 console.log('Anchor node not below marker value');
nickjillings@1324 2949 alert('Please keep listening');
nickjillings@1367 2950 this.storeErrorNode('Anchor node not below marker value');
nickjillings@1324 2951 return false;
nickjillings@1324 2952 }
nickjillings@2049 2953 }
nickjillings@2049 2954 }
nickjillings@2049 2955 return true;
nickjillings@2049 2956 };
nickjillings@2049 2957
nickjillings@2049 2958 this.checkHiddenReference = function()
nickjillings@2049 2959 {
nickjillings@1324 2960 for (var ao of audioEngineContext.audioObjects)
nickjillings@2049 2961 {
nickjillings@1324 2962 if (ao.specification.type == "reference")
nickjillings@2049 2963 {
nickjillings@1325 2964 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
nickjillings@1324 2965 // Anchor is not set below
nickjillings@1367 2966 console.log('Reference node not above marker value');
nickjillings@1367 2967 this.storeErrorNode('Reference node not above marker value');
nickjillings@1324 2968 alert('Please keep listening');
nickjillings@1324 2969 return false;
nickjillings@1324 2970 }
nickjillings@2049 2971 }
nickjillings@2049 2972 }
nickjillings@2049 2973 return true;
nickjillings@2049 2974 };
nickjillings@1474 2975
nickjillings@1474 2976 this.checkFragmentsFullyPlayed = function ()
nickjillings@1474 2977 {
nickjillings@1474 2978 // Checks the entire file has been played back
nickjillings@1474 2979 // NOTE ! This will return true IF playback is Looped!!!
nickjillings@1474 2980 if (audioEngineContext.loopPlayback)
nickjillings@1474 2981 {
nickjillings@1474 2982 console.log("WARNING - Looped source: Cannot check fragments are fully played");
nickjillings@1474 2983 return true;
nickjillings@1474 2984 }
nickjillings@1474 2985 var check_pass = true;
nickjillings@1474 2986 var error_obj = [];
nickjillings@1474 2987 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
nickjillings@1474 2988 {
nickjillings@1474 2989 var object = audioEngineContext.audioObjects[i];
nickjillings@1393 2990 var time = object.buffer.buffer.duration;
nickjillings@1474 2991 var metric = object.metric;
nickjillings@1474 2992 var passed = false;
nickjillings@1474 2993 for (var j=0; j<metric.listenTracker.length; j++)
nickjillings@1474 2994 {
nickjillings@1474 2995 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
nickjillings@1474 2996 var start_time = Number(bt[0].getAttribute('start'));
nickjillings@1474 2997 var stop_time = Number(bt[0].getAttribute('stop'));
nickjillings@1474 2998 var delta = stop_time - start_time;
nickjillings@1474 2999 if (delta >= time)
nickjillings@1474 3000 {
nickjillings@1474 3001 passed = true;
nickjillings@1474 3002 break;
nickjillings@1474 3003 }
nickjillings@1474 3004 }
nickjillings@1474 3005 if (passed == false)
nickjillings@1474 3006 {
nickjillings@1474 3007 check_pass = false;
nickjillings@1340 3008 console.log("Continue listening to track-"+audioEngineContext.audioObjects.interfaceDOM.getPresentedId());
nickjillings@1340 3009 error_obj.push(audioEngineContext.audioObjects.interfaceDOM.getPresentedId());
nickjillings@1474 3010 }
nickjillings@1474 3011 }
nickjillings@1474 3012 if (check_pass == false)
nickjillings@1474 3013 {
nickjillings@1393 3014 var str_start = "You have not completely listened to fragments ";
nickjillings@1474 3015 for (var i=0; i<error_obj.length; i++)
nickjillings@1474 3016 {
nickjillings@1474 3017 str_start += error_obj[i];
nickjillings@1474 3018 if (i != error_obj.length-1)
nickjillings@1474 3019 {
nickjillings@1474 3020 str_start += ', ';
nickjillings@1474 3021 }
nickjillings@1474 3022 }
nickjillings@1474 3023 str_start += ". Please keep listening";
nickjillings@1474 3024 console.log("[ALERT]: "+str_start);
nickjillings@1367 3025 this.storeErrorNode("[ALERT]: "+str_start);
nickjillings@1474 3026 alert(str_start);
nickjillings@1474 3027 }
nickjillings@1474 3028 };
nickjillings@1399 3029 this.checkAllMoved = function()
nickjillings@1399 3030 {
nickjillings@1399 3031 var str = "You have not moved ";
nickjillings@1399 3032 var failed = [];
nickjillings@1340 3033 for (var ao of audioEngineContext.audioObjects)
nickjillings@1399 3034 {
nickjillings@1340 3035 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
nickjillings@1399 3036 {
nickjillings@1340 3037 failed.push(ao.interfaceDOM.getPresentedId());
nickjillings@1399 3038 }
nickjillings@1399 3039 }
nickjillings@1399 3040 if (failed.length == 0)
nickjillings@1399 3041 {
nickjillings@1399 3042 return true;
nickjillings@1399 3043 } else if (failed.length == 1)
nickjillings@1399 3044 {
nickjillings@1399 3045 str += 'track '+failed[0];
nickjillings@1399 3046 } else {
nickjillings@1399 3047 str += 'tracks ';
nickjillings@1399 3048 for (var i=0; i<failed.length-1; i++)
nickjillings@1399 3049 {
nickjillings@1399 3050 str += failed[i]+', ';
nickjillings@1399 3051 }
nickjillings@1399 3052 str += 'and '+failed[i];
nickjillings@1399 3053 }
nickjillings@1399 3054 str +='.';
nickjillings@1399 3055 alert(str);
nickjillings@1399 3056 console.log(str);
nickjillings@1367 3057 this.storeErrorNode(str);
nickjillings@1399 3058 return false;
nickjillings@1399 3059 };
nickjillings@1399 3060 this.checkAllPlayed = function()
nickjillings@1399 3061 {
nickjillings@1399 3062 var str = "You have not played ";
nickjillings@1399 3063 var failed = [];
nickjillings@1340 3064 for (var ao of audioEngineContext.audioObjects)
nickjillings@1399 3065 {
nickjillings@1340 3066 if(ao.metric.wasListenedTo == false)
nickjillings@1399 3067 {
nickjillings@1340 3068 failed.push(ao.interfaceDOM.getPresentedId());
nickjillings@1399 3069 }
nickjillings@1399 3070 }
nickjillings@1399 3071 if (failed.length == 0)
nickjillings@1399 3072 {
nickjillings@1399 3073 return true;
nickjillings@1399 3074 } else if (failed.length == 1)
nickjillings@1399 3075 {
nickjillings@1399 3076 str += 'track '+failed[0];
nickjillings@1399 3077 } else {
nickjillings@1399 3078 str += 'tracks ';
nickjillings@1399 3079 for (var i=0; i<failed.length-1; i++)
nickjillings@1399 3080 {
nickjillings@1399 3081 str += failed[i]+', ';
nickjillings@1399 3082 }
nickjillings@1399 3083 str += 'and '+failed[i];
nickjillings@1399 3084 }
nickjillings@1399 3085 str +='.';
nickjillings@1399 3086 alert(str);
nickjillings@1399 3087 console.log(str);
nickjillings@1367 3088 this.storeErrorNode(str);
nickjillings@1399 3089 return false;
nickjillings@1399 3090 };
nickjillings@1367 3091
nickjillings@1367 3092 this.storeErrorNode = function(errorMessage)
nickjillings@1367 3093 {
nickjillings@1367 3094 var time = audioEngineContext.timer.getTestTime();
nickjillings@1367 3095 var node = storage.document.createElement('error');
nickjillings@1367 3096 node.setAttribute('time',time);
nickjillings@1367 3097 node.textContent = errorMessage;
nickjillings@1367 3098 testState.currentStore.XMLDOM.appendChild(node);
nickjillings@1367 3099 };
nickjillings@1324 3100 }
nickjillings@1324 3101
nickjillings@1324 3102 function Storage()
nickjillings@1324 3103 {
nickjillings@1324 3104 // Holds results in XML format until ready for collection
nickjillings@1324 3105 this.globalPreTest = null;
nickjillings@1324 3106 this.globalPostTest = null;
nickjillings@1324 3107 this.testPages = [];
nickjillings@1324 3108 this.document = document.implementation.createDocument(null,"waetresult");
nickjillings@2118 3109 this.root = this.document.childNodes[0];
nickjillings@1324 3110 this.state = 0;
nickjillings@1324 3111
nickjillings@2147 3112 this.initialise = function(sessionKey)
nickjillings@1324 3113 {
nickjillings@2147 3114 if (sessionKey == undefined) {
nickjillings@2147 3115 // We need to get the sessionKey
nickjillings@2147 3116 this.SessionKey.generateKey();
nickjillings@2150 3117 var projectDocument = specification.projectXML;
nickjillings@2150 3118 projectDocument.setAttribute('file-name',url);
nickjillings@2150 3119 this.root.appendChild(projectDocument);
nickjillings@2150 3120 this.root.appendChild(returnDateNode());
nickjillings@2150 3121 this.root.appendChild(interfaceContext.returnNavigator());
nickjillings@2147 3122 } else {
nickjillings@2147 3123 this.SessionKey.key = sessionKey;
nickjillings@2147 3124 }
nickjillings@2150 3125 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
nickjillings@2150 3126 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
nickjillings@1324 3127 };
nickjillings@2147 3128
nickjillings@2147 3129 this.SessionKey = {
nickjillings@2147 3130 key: null,
nickjillings@2147 3131 request: new XMLHttpRequest(),
nickjillings@2147 3132 parent: this,
nickjillings@2147 3133 handleEvent: function() {
nickjillings@2147 3134 var parse = new DOMParser();
nickjillings@2147 3135 var xml = parse.parseFromString(this.request.response,"text/xml");
nickjillings@2147 3136 if (xml.getAllElementsByTagName("state")[0].textContent == "OK") {
nickjillings@2147 3137 this.key = xml.getAllElementsByTagName("key")[0].textContent;
nickjillings@2150 3138 this.parent.root.setAttribute("key",this.key);
nickjillings@2150 3139 this.parent.root.setAttribute("state","empty");
nickjillings@2147 3140 } else {
nickjillings@2147 3141 this.generateKey();
nickjillings@2147 3142 }
nickjillings@2147 3143 },
nickjillings@2147 3144 generateKey: function() {
nickjillings@2147 3145 var temp_key = randomString(32);
nickjillings@2147 3146 this.request.open("GET","keygen.php?key="+temp_key,true);
nickjillings@2147 3147 this.request.addEventListener("load",this);
nickjillings@2147 3148 this.request.send();
nickjillings@2149 3149 },
nickjillings@2150 3150 update: function() {
nickjillings@2150 3151 this.parent.root.setAttribute("state","update");
nickjillings@2150 3152 var xmlhttp = new XMLHttpRequest();
nickjillings@2150 3153 xmlhttp.open("POST",specification.projectReturn+"?key="+this.key);
nickjillings@2150 3154 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
nickjillings@2150 3155 xmlhttp.onerror = function(){
nickjillings@2150 3156 console.log('Error updating file to server!');
nickjillings@2150 3157 };
nickjillings@2150 3158 var hold = document.createElement("div");
nickjillings@2150 3159 var clone = this.parent.root.cloneNode(true);
nickjillings@2150 3160 hold.appendChild(clone);
nickjillings@2150 3161 xmlhttp.onload = function() {
nickjillings@2150 3162 if (this.status >= 300) {
nickjillings@2150 3163 console.log("WARNING - Could not update at this time");
nickjillings@2150 3164 } else {
nickjillings@2150 3165 var parser = new DOMParser();
nickjillings@2150 3166 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
nickjillings@2150 3167 var response = xmlDoc.getElementsByTagName('response')[0];
nickjillings@2150 3168 if (response.getAttribute("state") == "OK") {
nickjillings@2150 3169 var file = response.getElementsByTagName("file")[0];
nickjillings@2150 3170 console.log("Intermediate save: OK, written "+file.getAttribute("bytes")+"B");
nickjillings@2150 3171 } else {
nickjillings@2150 3172 var message = response.getElementsByTagName("message");
nickjillings@2150 3173 console.log("Intermediate save: Error! "+message.textContent);
nickjillings@2150 3174 }
nickjillings@2149 3175 }
nickjillings@2149 3176 }
nickjillings@2150 3177 xmlhttp.send([hold.innerHTML]);
nickjillings@2147 3178 }
nickjillings@2147 3179 }
nickjillings@1324 3180
nickjillings@1324 3181 this.createTestPageStore = function(specification)
nickjillings@1324 3182 {
nickjillings@1324 3183 var store = new this.pageNode(this,specification);
nickjillings@1324 3184 this.testPages.push(store);
nickjillings@1324 3185 return this.testPages[this.testPages.length-1];
nickjillings@1324 3186 };
nickjillings@1324 3187
nickjillings@1324 3188 this.surveyNode = function(parent,root,specification)
nickjillings@1324 3189 {
nickjillings@1324 3190 this.specification = specification;
nickjillings@1324 3191 this.parent = parent;
nickjillings@1324 3192 this.XMLDOM = this.parent.document.createElement('survey');
nickjillings@1324 3193 this.XMLDOM.setAttribute('location',this.specification.location);
nickjillings@1324 3194 for (var optNode of this.specification.options)
nickjillings@1324 3195 {
nickjillings@1324 3196 if (optNode.type != 'statement')
nickjillings@1324 3197 {
nickjillings@1324 3198 var node = this.parent.document.createElement('surveyresult');
nickjillings@1324 3199 node.id = optNode.id;
nickjillings@1324 3200 node.setAttribute('type',optNode.type);
nickjillings@1324 3201 this.XMLDOM.appendChild(node);
nickjillings@1324 3202 }
nickjillings@1324 3203 }
nickjillings@1324 3204 root.appendChild(this.XMLDOM);
nickjillings@1324 3205
nickjillings@1324 3206 this.postResult = function(node)
nickjillings@1324 3207 {
nickjillings@1324 3208 // From popup: node is the popupOption node containing both spec. and results
nickjillings@1324 3209 // ID is the position
nickjillings@1324 3210 if (node.specification.type == 'statement'){return;}
nickjillings@1324 3211 var surveyresult = this.parent.document.getElementById(node.specification.id);
nickjillings@1324 3212 switch(node.specification.type)
nickjillings@1324 3213 {
nickjillings@1324 3214 case "number":
nickjillings@1324 3215 case "question":
nickjillings@1324 3216 var child = this.parent.document.createElement('response');
nickjillings@1324 3217 child.textContent = node.response;
nickjillings@1324 3218 surveyresult.appendChild(child);
nickjillings@1324 3219 break;
nickjillings@1324 3220 case "radio":
nickjillings@1324 3221 var child = this.parent.document.createElement('response');
nickjillings@1324 3222 child.setAttribute('name',node.response.name);
nickjillings@1324 3223 child.textContent = node.response.text;
nickjillings@1324 3224 surveyresult.appendChild(child);
nickjillings@1324 3225 break;
nickjillings@1324 3226 case "checkbox":
nickjillings@1324 3227 for (var i=0; i<node.response.length; i++)
nickjillings@1324 3228 {
nickjillings@1324 3229 var checkNode = this.parent.document.createElement('response');
nickjillings@1347 3230 checkNode.setAttribute('name',node.response[i].name);
nickjillings@1347 3231 checkNode.setAttribute('checked',node.response[i].checked);
nickjillings@1326 3232 surveyresult.appendChild(checkNode);
nickjillings@1324 3233 }
nickjillings@1324 3234 break;
nickjillings@1324 3235 }
nickjillings@1324 3236 };
nickjillings@1324 3237 };
nickjillings@1324 3238
nickjillings@1324 3239 this.pageNode = function(parent,specification)
nickjillings@1324 3240 {
nickjillings@1324 3241 // Create one store per test page
nickjillings@1324 3242 this.specification = specification;
nickjillings@1324 3243 this.parent = parent;
nickjillings@1324 3244 this.XMLDOM = this.parent.document.createElement('page');
nickjillings@1324 3245 this.XMLDOM.setAttribute('id',specification.id);
nickjillings@1324 3246 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
nickjillings@1345 3247 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
nickjillings@1345 3248 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
nickjillings@1324 3249
nickjillings@1324 3250 // Add any page metrics
nickjillings@1324 3251 var page_metric = this.parent.document.createElement('metric');
nickjillings@1324 3252 this.XMLDOM.appendChild(page_metric);
nickjillings@1324 3253
nickjillings@1324 3254 // Add the audioelement
nickjillings@1324 3255 for (var element of this.specification.audioElements)
nickjillings@1324 3256 {
nickjillings@1324 3257 var aeNode = this.parent.document.createElement('audioelement');
nickjillings@1324 3258 aeNode.id = element.id;
nickjillings@1324 3259 aeNode.setAttribute('type',element.type);
nickjillings@1324 3260 aeNode.setAttribute('url', element.url);
nickjillings@1324 3261 aeNode.setAttribute('gain', element.gain);
nickjillings@1324 3262 if (element.type == 'anchor' || element.type == 'reference')
nickjillings@1324 3263 {
nickjillings@1324 3264 if (element.marker > 0)
nickjillings@1324 3265 {
nickjillings@1324 3266 aeNode.setAttribute('marker',element.marker);
nickjillings@1324 3267 }
nickjillings@1324 3268 }
nickjillings@1324 3269 var ae_metric = this.parent.document.createElement('metric');
nickjillings@1324 3270 aeNode.appendChild(ae_metric);
nickjillings@1324 3271 this.XMLDOM.appendChild(aeNode);
nickjillings@1324 3272 }
nickjillings@1324 3273
nickjillings@1324 3274 this.parent.root.appendChild(this.XMLDOM);
nickjillings@1324 3275 };
nickjillings@2150 3276 this.update = function() {
nickjillings@2150 3277 this.SessionKey.update();
nickjillings@2150 3278 }
nickjillings@1324 3279 this.finish = function()
nickjillings@1324 3280 {
nickjillings@1324 3281 if (this.state == 0)
nickjillings@1324 3282 {
nickjillings@2150 3283 this.update();
nickjillings@1324 3284 }
nickjillings@1324 3285 this.state = 1;
nickjillings@1324 3286 return this.root;
nickjillings@1324 3287 };
nickjillings@1324 3288 }