giuliomoro@0: /** giuliomoro@0: * core.js giuliomoro@0: * giuliomoro@0: * Main script to run, calls all other core functions and manages loading/store to backend. giuliomoro@0: * Also contains all global variables. giuliomoro@0: */ giuliomoro@0: giuliomoro@0: /* create the web audio API context and store in audioContext*/ giuliomoro@0: var audioContext; // Hold the browser web audio API giuliomoro@0: var projectXML; // Hold the parsed setup XML giuliomoro@0: var schemaXSD; // Hold the parsed schema XSD giuliomoro@0: var specification; giuliomoro@0: var interfaceContext; giuliomoro@0: var storage; giuliomoro@0: var popup; // Hold the interfacePopup object giuliomoro@0: var testState; giuliomoro@0: var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order giuliomoro@0: var audioEngineContext; // The custom AudioEngine object giuliomoro@0: var projectReturn; giuliomoro@0: var returnUrl; // Holds the url to be redirected to at the end of the test giuliomoro@0: giuliomoro@0: // Add a prototype to the bufferSourceNode to reference to the audioObject holding it giuliomoro@0: AudioBufferSourceNode.prototype.owner = undefined; giuliomoro@0: // Add a prototype to the bufferSourceNode to hold when the object was given a play command giuliomoro@0: AudioBufferSourceNode.prototype.playbackStartTime = undefined; giuliomoro@0: // Add a prototype to the bufferNode to hold the desired LINEAR gain giuliomoro@0: AudioBuffer.prototype.playbackGain = undefined; giuliomoro@0: // Add a prototype to the bufferNode to hold the computed LUFS loudness giuliomoro@0: AudioBuffer.prototype.lufs = undefined; giuliomoro@0: giuliomoro@0: // Firefox does not have an XMLDocument.prototype.getElementsByName giuliomoro@0: // and there is no searchAll style command, this custom function will giuliomoro@0: // search all children recusrively for the name. Used for XSD where all giuliomoro@0: // element nodes must have a name and therefore can pull the schema node giuliomoro@0: XMLDocument.prototype.getAllElementsByName = function(name) giuliomoro@0: { giuliomoro@0: name = String(name); giuliomoro@0: var selected = this.documentElement.getAllElementsByName(name); giuliomoro@0: return selected; giuliomoro@0: } giuliomoro@0: giuliomoro@0: Element.prototype.getAllElementsByName = function(name) giuliomoro@0: { giuliomoro@0: name = String(name); giuliomoro@0: var selected = []; giuliomoro@0: var node = this.firstElementChild; giuliomoro@0: while(node != null) giuliomoro@0: { giuliomoro@0: if (node.getAttribute('name') == name) giuliomoro@0: { giuliomoro@0: selected.push(node); giuliomoro@0: } giuliomoro@0: if (node.childElementCount > 0) giuliomoro@0: { giuliomoro@0: selected = selected.concat(node.getAllElementsByName(name)); giuliomoro@0: } giuliomoro@0: node = node.nextElementSibling; giuliomoro@0: } giuliomoro@0: return selected; giuliomoro@0: } giuliomoro@0: giuliomoro@0: XMLDocument.prototype.getAllElementsByTagName = function(name) giuliomoro@0: { giuliomoro@0: name = String(name); giuliomoro@0: var selected = this.documentElement.getAllElementsByTagName(name); giuliomoro@0: return selected; giuliomoro@0: } giuliomoro@0: giuliomoro@0: Element.prototype.getAllElementsByTagName = function(name) giuliomoro@0: { giuliomoro@0: name = String(name); giuliomoro@0: var selected = []; giuliomoro@0: var node = this.firstElementChild; giuliomoro@0: while(node != null) giuliomoro@0: { giuliomoro@0: if (node.nodeName == name) giuliomoro@0: { giuliomoro@0: selected.push(node); giuliomoro@0: } giuliomoro@0: if (node.childElementCount > 0) giuliomoro@0: { giuliomoro@0: selected = selected.concat(node.getAllElementsByTagName(name)); giuliomoro@0: } giuliomoro@0: node = node.nextElementSibling; giuliomoro@0: } giuliomoro@0: return selected; giuliomoro@0: } giuliomoro@0: giuliomoro@0: // Firefox does not have an XMLDocument.prototype.getElementsByName giuliomoro@0: if (typeof XMLDocument.prototype.getElementsByName != "function") { giuliomoro@0: XMLDocument.prototype.getElementsByName = function(name) giuliomoro@0: { giuliomoro@0: name = String(name); giuliomoro@0: var node = this.documentElement.firstElementChild; giuliomoro@0: var selected = []; giuliomoro@0: while(node != null) giuliomoro@0: { giuliomoro@0: if (node.getAttribute('name') == name) giuliomoro@0: { giuliomoro@0: selected.push(node); giuliomoro@0: } giuliomoro@0: node = node.nextElementSibling; giuliomoro@0: } giuliomoro@0: return selected; giuliomoro@0: } giuliomoro@0: } giuliomoro@0: giuliomoro@0: window.onload = function() { giuliomoro@0: // Function called once the browser has loaded all files. giuliomoro@0: // This should perform any initial commands such as structure / loading documents giuliomoro@0: giuliomoro@0: // Create a web audio API context giuliomoro@0: // Fixed for cross-browser support giuliomoro@0: var AudioContext = window.AudioContext || window.webkitAudioContext; giuliomoro@0: audioContext = new AudioContext; giuliomoro@0: giuliomoro@0: // Create test state giuliomoro@0: testState = new stateMachine(); giuliomoro@0: giuliomoro@0: // Create the popup interface object giuliomoro@0: popup = new interfacePopup(); giuliomoro@0: giuliomoro@0: // Create the specification object giuliomoro@0: specification = new Specification(); giuliomoro@0: giuliomoro@0: // Create the interface object giuliomoro@0: interfaceContext = new Interface(specification); giuliomoro@0: giuliomoro@0: // Create the storage object giuliomoro@0: storage = new Storage(); giuliomoro@0: // Define window callbacks for interface giuliomoro@0: window.onresize = function(event){interfaceContext.resizeWindow(event);}; giuliomoro@0: }; giuliomoro@0: giuliomoro@0: function loadProjectSpec(url) { giuliomoro@0: // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data giuliomoro@0: // If url is null, request client to upload project XML document giuliomoro@0: var xmlhttp = new XMLHttpRequest(); giuliomoro@0: xmlhttp.open("GET",'test-schema.xsd',true); giuliomoro@0: xmlhttp.onload = function() giuliomoro@0: { giuliomoro@0: schemaXSD = xmlhttp.response; giuliomoro@0: var parse = new DOMParser(); giuliomoro@0: specification.schema = parse.parseFromString(xmlhttp.response,'text/xml'); giuliomoro@0: var r = new XMLHttpRequest(); giuliomoro@0: r.open('GET',url,true); giuliomoro@0: r.onload = function() { giuliomoro@0: loadProjectSpecCallback(r.response); giuliomoro@0: }; giuliomoro@0: r.onerror = function() { giuliomoro@0: document.getElementsByTagName('body')[0].innerHTML = null; giuliomoro@0: var msg = document.createElement("h3"); giuliomoro@0: msg.textContent = "FATAL ERROR"; giuliomoro@0: var span = document.createElement("p"); giuliomoro@0: 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."; giuliomoro@0: document.getElementsByTagName('body')[0].appendChild(msg); giuliomoro@0: document.getElementsByTagName('body')[0].appendChild(span); giuliomoro@0: } giuliomoro@0: r.send(); giuliomoro@0: }; giuliomoro@0: xmlhttp.send(); giuliomoro@0: }; giuliomoro@0: giuliomoro@0: function loadProjectSpecCallback(response) { giuliomoro@0: // Function called after asynchronous download of XML project specification giuliomoro@0: //var decode = $.parseXML(response); giuliomoro@0: //projectXML = $(decode); giuliomoro@0: giuliomoro@0: // Check if XML is new or a resumption giuliomoro@0: var parse = new DOMParser(); giuliomoro@0: var responseDocument = parse.parseFromString(response,'text/xml'); giuliomoro@0: var errorNode = responseDocument.getElementsByTagName('parsererror'); giuliomoro@0: if (errorNode.length >= 1) giuliomoro@0: { giuliomoro@0: var msg = document.createElement("h3"); giuliomoro@0: msg.textContent = "FATAL ERROR"; giuliomoro@0: var span = document.createElement("span"); giuliomoro@0: span.textContent = "The XML parser returned the following errors when decoding your XML file"; giuliomoro@0: document.getElementsByTagName('body')[0].innerHTML = null; giuliomoro@0: document.getElementsByTagName('body')[0].appendChild(msg); giuliomoro@0: document.getElementsByTagName('body')[0].appendChild(span); giuliomoro@0: document.getElementsByTagName('body')[0].appendChild(errorNode[0]); giuliomoro@0: return; giuliomoro@0: } giuliomoro@0: if (responseDocument.children[0].nodeName == "waet") { giuliomoro@0: // document is a specification giuliomoro@0: giuliomoro@0: // Perform XML schema validation giuliomoro@0: var Module = { giuliomoro@0: xml: response, giuliomoro@0: schema: schemaXSD, giuliomoro@0: arguments:["--noout", "--schema", 'test-schema.xsd','document.xml'] giuliomoro@0: }; giuliomoro@0: projectXML = responseDocument; giuliomoro@0: var xmllint = validateXML(Module); giuliomoro@0: console.log(xmllint); giuliomoro@0: if(xmllint != 'document.xml validates\n') giuliomoro@0: { giuliomoro@0: document.getElementsByTagName('body')[0].innerHTML = null; giuliomoro@0: var msg = document.createElement("h3"); giuliomoro@0: msg.textContent = "FATAL ERROR"; giuliomoro@0: var span = document.createElement("h4"); giuliomoro@0: span.textContent = "The XML validator returned the following errors when decoding your XML file"; giuliomoro@0: document.getElementsByTagName('body')[0].appendChild(msg); giuliomoro@0: document.getElementsByTagName('body')[0].appendChild(span); giuliomoro@0: xmllint = xmllint.split('\n'); giuliomoro@0: for (var i in xmllint) giuliomoro@0: { giuliomoro@0: document.getElementsByTagName('body')[0].appendChild(document.createElement('br')); giuliomoro@0: var span = document.createElement("span"); giuliomoro@0: span.textContent = xmllint[i]; giuliomoro@0: document.getElementsByTagName('body')[0].appendChild(span); giuliomoro@0: } giuliomoro@0: return; giuliomoro@0: } giuliomoro@0: // Build the specification giuliomoro@0: specification.decode(projectXML); giuliomoro@0: // Generate the session-key giuliomoro@0: storage.initialise(); giuliomoro@0: giuliomoro@0: } else if (responseDocument.children[0].nodeName == "waetresult") { giuliomoro@0: // document is a result giuliomoro@0: projectXML = document.implementation.createDocument(null,"waet"); giuliomoro@0: projectXML.children[0].appendChild(responseDocument.getElementsByTagName('waet')[0].getElementsByTagName("setup")[0].cloneNode(true)); giuliomoro@0: var child = responseDocument.children[0].children[0]; giuliomoro@0: while (child != null) { giuliomoro@0: if (child.nodeName == "survey") { giuliomoro@0: // One of the global survey elements giuliomoro@0: if (child.getAttribute("state") == "complete") { giuliomoro@0: // We need to remove this survey from giuliomoro@0: var location = child.getAttribute("location"); giuliomoro@0: var globalSurveys = projectXML.getElementsByTagName("setup")[0].getElementsByTagName("survey")[0]; giuliomoro@0: while(globalSurveys != null) { giuliomoro@0: if (location == "pre" || location == "before") { giuliomoro@0: if (globalSurveys.getAttribute("location") == "pre" || globalSurveys.getAttribute("location") == "before") { giuliomoro@0: projectXML.getElementsByTagName("setup")[0].removeChild(globalSurveys); giuliomoro@0: break; giuliomoro@0: } giuliomoro@0: } else { giuliomoro@0: if (globalSurveys.getAttribute("location") == "post" || globalSurveys.getAttribute("location") == "after") { giuliomoro@0: projectXML.getElementsByTagName("setup")[0].removeChild(globalSurveys); giuliomoro@0: break; giuliomoro@0: } giuliomoro@0: } giuliomoro@0: globalSurveys = globalSurveys.nextElementSibling; giuliomoro@0: } giuliomoro@0: } else { giuliomoro@0: // We need to complete this, so it must be regenerated by store giuliomoro@0: var copy = child; giuliomoro@0: child = child.previousElementSibling; giuliomoro@0: responseDocument.children[0].removeChild(copy); giuliomoro@0: } giuliomoro@0: } else if (child.nodeName == "page") { giuliomoro@0: if (child.getAttribute("state") == "empty") { giuliomoro@0: // We need to complete this page giuliomoro@0: projectXML.children[0].appendChild(responseDocument.getElementById(child.getAttribute("ref")).cloneNode(true)); giuliomoro@0: var copy = child; giuliomoro@0: child = child.previousElementSibling; giuliomoro@0: responseDocument.children[0].removeChild(copy); giuliomoro@0: } giuliomoro@0: } giuliomoro@0: child = child.nextElementSibling; giuliomoro@0: } giuliomoro@0: // Build the specification giuliomoro@0: specification.decode(projectXML); giuliomoro@0: // Use the original giuliomoro@0: storage.initialise(responseDocument); giuliomoro@0: } giuliomoro@0: /// CHECK FOR SAMPLE RATE COMPATIBILITY giuliomoro@0: if (specification.sampleRate != undefined) { giuliomoro@0: if (Number(specification.sampleRate) != audioContext.sampleRate) { giuliomoro@0: 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.'; giuliomoro@0: alert(errStr); giuliomoro@0: return; giuliomoro@0: } giuliomoro@0: } giuliomoro@0: giuliomoro@0: // Detect the interface to use and load the relevant javascripts. giuliomoro@0: var interfaceJS = document.createElement('script'); giuliomoro@0: interfaceJS.setAttribute("type","text/javascript"); giuliomoro@0: switch(specification.interface) giuliomoro@0: { giuliomoro@0: case "APE": giuliomoro@0: interfaceJS.setAttribute("src","interfaces/ape.js"); giuliomoro@0: giuliomoro@0: // APE comes with a css file giuliomoro@0: var css = document.createElement('link'); giuliomoro@0: css.rel = 'stylesheet'; giuliomoro@0: css.type = 'text/css'; giuliomoro@0: css.href = 'interfaces/ape.css'; giuliomoro@0: giuliomoro@0: document.getElementsByTagName("head")[0].appendChild(css); giuliomoro@0: break; giuliomoro@0: giuliomoro@0: case "MUSHRA": giuliomoro@0: interfaceJS.setAttribute("src","interfaces/mushra.js"); giuliomoro@0: giuliomoro@0: // MUSHRA comes with a css file giuliomoro@0: var css = document.createElement('link'); giuliomoro@0: css.rel = 'stylesheet'; giuliomoro@0: css.type = 'text/css'; giuliomoro@0: css.href = 'interfaces/mushra.css'; giuliomoro@0: giuliomoro@0: document.getElementsByTagName("head")[0].appendChild(css); giuliomoro@0: break; giuliomoro@0: giuliomoro@0: case "AB": giuliomoro@6: interfaceJS.setAttribute("src","interfaces/AB.js?"+Math.random()); giuliomoro@0: giuliomoro@0: // AB comes with a css file giuliomoro@0: var css = document.createElement('link'); giuliomoro@0: css.rel = 'stylesheet'; giuliomoro@0: css.type = 'text/css'; giuliomoro@1: css.href = 'interfaces/AB.css?'+Math.random(); giuliomoro@0: giuliomoro@0: document.getElementsByTagName("head")[0].appendChild(css); giuliomoro@0: break; giuliomoro@0: case "Bipolar": giuliomoro@0: case "ACR": giuliomoro@0: case "DCR": giuliomoro@0: case "CCR": giuliomoro@0: case "ABC": giuliomoro@0: // Above enumerate to horizontal sliders giuliomoro@0: interfaceJS.setAttribute("src","interfaces/horizontal-sliders.js"); giuliomoro@0: giuliomoro@0: // horizontal-sliders comes with a css file giuliomoro@0: var css = document.createElement('link'); giuliomoro@0: css.rel = 'stylesheet'; giuliomoro@0: css.type = 'text/css'; giuliomoro@0: css.href = 'interfaces/horizontal-sliders.css'; giuliomoro@0: giuliomoro@0: document.getElementsByTagName("head")[0].appendChild(css); giuliomoro@0: break; giuliomoro@0: case "discrete": giuliomoro@0: case "likert": giuliomoro@0: // Above enumerate to horizontal discrete radios giuliomoro@0: interfaceJS.setAttribute("src","interfaces/discrete.js"); giuliomoro@0: giuliomoro@0: // horizontal-sliders comes with a css file giuliomoro@0: var css = document.createElement('link'); giuliomoro@0: css.rel = 'stylesheet'; giuliomoro@0: css.type = 'text/css'; giuliomoro@0: css.href = 'interfaces/discrete.css'; giuliomoro@0: giuliomoro@0: document.getElementsByTagName("head")[0].appendChild(css); giuliomoro@0: break; giuliomoro@0: } giuliomoro@0: document.getElementsByTagName("head")[0].appendChild(interfaceJS); giuliomoro@0: giuliomoro@0: // Create the audio engine object giuliomoro@0: audioEngineContext = new AudioEngine(specification); giuliomoro@0: } giuliomoro@0: giuliomoro@0: function createProjectSave(destURL) { giuliomoro@0: // Save the data from interface into XML and send to destURL giuliomoro@0: // If destURL is null then download XML in client giuliomoro@0: // Now time to render file locally giuliomoro@0: var xmlDoc = interfaceXMLSave(); giuliomoro@0: var parent = document.createElement("div"); giuliomoro@0: parent.appendChild(xmlDoc); giuliomoro@0: var file = [parent.innerHTML]; giuliomoro@0: if (destURL == "local") { giuliomoro@0: var bb = new Blob(file,{type : 'application/xml'}); giuliomoro@0: var dnlk = window.URL.createObjectURL(bb); giuliomoro@0: var a = document.createElement("a"); giuliomoro@0: a.hidden = ''; giuliomoro@0: a.href = dnlk; giuliomoro@0: a.download = "save.xml"; giuliomoro@0: a.textContent = "Save File"; giuliomoro@0: giuliomoro@0: popup.showPopup(); giuliomoro@0: popup.popupContent.innerHTML = "Please save the file below to give to your test supervisor
"; giuliomoro@0: popup.popupContent.appendChild(a); giuliomoro@0: } else { giuliomoro@0: destUrlFull = destURL+"?key="+storage.SessionKey.key; giuliomoro@0: var saveFilenamePrefix; giuliomoro@0: // parse the querystring of destUrl, get the "id" (if any) and append it to destUrl giuliomoro@0: if(typeof(returnUrl) !== "undefined"){ giuliomoro@0: var qs = returnUrl.split("?"); giuliomoro@0: if(qs.length == 2){ giuliomoro@0: qs = qs[1]; giuliomoro@0: qs = qs.split("&"); giuliomoro@0: for(var n = 0; n < qs.length; n++){ giuliomoro@0: var pair = qs[n].split("="); giuliomoro@0: if (pair[0] == "id") { giuliomoro@0: saveFilenamePrefix = pair[1]; giuliomoro@0: } giuliomoro@0: } giuliomoro@0: } giuliomoro@0: } giuliomoro@0: if(typeof(saveFilenamePrefix) !== "undefined"){ giuliomoro@0: destUrlFull+="&saveFilenamePrefix="+saveFilenamePrefix; giuliomoro@0: } giuliomoro@0: var xmlhttp = new XMLHttpRequest; giuliomoro@0: xmlhttp.open("POST",destUrlFull,true); giuliomoro@0: xmlhttp.setRequestHeader('Content-Type', 'text/xml'); giuliomoro@0: xmlhttp.onerror = function(){ giuliomoro@0: console.log('Error saving file to server! Presenting download locally'); giuliomoro@0: createProjectSave("local"); giuliomoro@0: }; giuliomoro@0: xmlhttp.onload = function() { giuliomoro@0: console.log(xmlhttp); giuliomoro@0: if (this.status >= 300) { giuliomoro@0: console.log("WARNING - Could not update at this time"); giuliomoro@0: createProjectSave("local"); giuliomoro@0: } else { giuliomoro@0: var parser = new DOMParser(); giuliomoro@0: var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml"); giuliomoro@0: var response = xmlDoc.getElementsByTagName('response')[0]; giuliomoro@0: window.onbeforeunload=null; giuliomoro@0: if (response.getAttribute("state") == "OK") { giuliomoro@0: var file = response.getElementsByTagName("file")[0]; giuliomoro@0: if(typeof(returnUrl) !== "undefined"){ giuliomoro@0: window.location = returnUrl; giuliomoro@0: } giuliomoro@0: console.log("Save: OK, written "+file.getAttribute("bytes")+"B"); giuliomoro@0: popup.popupContent.textContent = "Thank you. Your session has been saved."; giuliomoro@0: } else { giuliomoro@0: var message = response.getElementsByTagName("message"); giuliomoro@0: console.log("Save: Error! "+message.textContent); giuliomoro@0: createProjectSave("local"); giuliomoro@0: } giuliomoro@0: } giuliomoro@0: }; giuliomoro@0: xmlhttp.send(file); giuliomoro@0: popup.showPopup(); giuliomoro@0: popup.popupContent.innerHTML = null; giuliomoro@0: popup.popupContent.textContent = "Submitting. Please Wait"; giuliomoro@0: popup.hideNextButton(); giuliomoro@0: popup.hidePreviousButton(); giuliomoro@0: } giuliomoro@0: } giuliomoro@0: giuliomoro@0: function errorSessionDump(msg){ giuliomoro@0: // Create the partial interface XML save giuliomoro@0: // Include error node with message on why the dump occured giuliomoro@0: popup.showPopup(); giuliomoro@0: popup.popupContent.innerHTML = null; giuliomoro@0: var err = document.createElement('error'); giuliomoro@0: var parent = document.createElement("div"); giuliomoro@0: if (typeof msg === "object") giuliomoro@0: { giuliomoro@0: err.appendChild(msg); giuliomoro@0: popup.popupContent.appendChild(msg); giuliomoro@0: giuliomoro@0: } else { giuliomoro@0: err.textContent = msg; giuliomoro@0: popup.popupContent.innerHTML = "ERROR : "+msg; giuliomoro@0: } giuliomoro@0: var xmlDoc = interfaceXMLSave(); giuliomoro@0: xmlDoc.appendChild(err); giuliomoro@0: parent.appendChild(xmlDoc); giuliomoro@0: var file = [parent.innerHTML]; giuliomoro@0: var bb = new Blob(file,{type : 'application/xml'}); giuliomoro@0: var dnlk = window.URL.createObjectURL(bb); giuliomoro@0: var a = document.createElement("a"); giuliomoro@0: a.hidden = ''; giuliomoro@0: a.href = dnlk; giuliomoro@0: a.download = "save.xml"; giuliomoro@0: a.textContent = "Save File"; giuliomoro@0: giuliomoro@0: giuliomoro@0: giuliomoro@0: popup.popupContent.appendChild(a); giuliomoro@0: } giuliomoro@0: giuliomoro@0: // Only other global function which must be defined in the interface class. Determines how to create the XML document. giuliomoro@0: function interfaceXMLSave(){ giuliomoro@0: // Create the XML string to be exported with results giuliomoro@0: return storage.finish(); giuliomoro@0: } giuliomoro@0: giuliomoro@0: function linearToDecibel(gain) giuliomoro@0: { giuliomoro@0: return 20.0*Math.log10(gain); giuliomoro@0: } giuliomoro@0: giuliomoro@0: function decibelToLinear(gain) giuliomoro@0: { giuliomoro@0: return Math.pow(10,gain/20.0); giuliomoro@0: } giuliomoro@0: giuliomoro@0: function randomString(length) { giuliomoro@0: return Math.round((Math.pow(36, length + 1) - Math.random() * Math.pow(36, length))).toString(36).slice(1); giuliomoro@0: } giuliomoro@0: giuliomoro@0: function interfacePopup() { giuliomoro@0: // Creates an object to manage the popup giuliomoro@0: this.popup = null; giuliomoro@0: this.popupContent = null; giuliomoro@0: this.popupTitle = null; giuliomoro@0: this.popupResponse = null; giuliomoro@0: this.buttonProceed = null; giuliomoro@0: this.buttonPrevious = null; giuliomoro@0: this.popupOptions = null; giuliomoro@0: this.currentIndex = null; giuliomoro@0: this.node = null; giuliomoro@0: this.store = null; giuliomoro@0: $(window).keypress(function(e){ giuliomoro@0: if (e.keyCode == 13 && popup.popup.style.visibility == 'visible') giuliomoro@0: { giuliomoro@0: console.log(e); giuliomoro@0: popup.buttonProceed.onclick(); giuliomoro@0: e.preventDefault(); giuliomoro@0: } giuliomoro@0: }); giuliomoro@0: giuliomoro@0: this.createPopup = function(){ giuliomoro@0: // Create popup window interface giuliomoro@0: var insertPoint = document.getElementById("topLevelBody"); giuliomoro@0: giuliomoro@0: this.popup = document.getElementById('popupHolder'); giuliomoro@0: this.popup.style.left = (window.innerWidth/2)-250 + 'px'; giuliomoro@0: this.popup.style.top = (window.innerHeight/2)-125 + 'px'; giuliomoro@0: giuliomoro@0: this.popupContent = document.getElementById('popupContent'); giuliomoro@0: giuliomoro@0: this.popupTitle = document.getElementById('popupTitle'); giuliomoro@0: giuliomoro@0: this.popupResponse = document.getElementById('popupResponse'); giuliomoro@0: giuliomoro@0: this.buttonProceed = document.getElementById('popup-proceed'); giuliomoro@0: this.buttonProceed.onclick = function(){popup.proceedClicked();}; giuliomoro@0: giuliomoro@0: this.buttonPrevious = document.getElementById('popup-previous'); giuliomoro@0: this.buttonPrevious.onclick = function(){popup.previousClick();}; giuliomoro@0: giuliomoro@0: this.hidePopup(); giuliomoro@0: giuliomoro@0: this.popup.style.zIndex = -1; giuliomoro@0: this.popup.style.visibility = 'hidden'; giuliomoro@0: }; giuliomoro@0: giuliomoro@0: this.showPopup = function(){ giuliomoro@0: if (this.popup == null) { giuliomoro@0: this.createPopup(); giuliomoro@0: } giuliomoro@0: this.popup.style.zIndex = 3; giuliomoro@0: this.popup.style.visibility = 'visible'; giuliomoro@0: var blank = document.getElementsByClassName('testHalt')[0]; giuliomoro@0: blank.style.zIndex = 2; giuliomoro@0: blank.style.visibility = 'visible'; giuliomoro@0: }; giuliomoro@0: giuliomoro@0: this.hidePopup = function(){ giuliomoro@0: this.popup.style.zIndex = -1; giuliomoro@0: this.popup.style.visibility = 'hidden'; giuliomoro@0: var blank = document.getElementsByClassName('testHalt')[0]; giuliomoro@0: blank.style.zIndex = -2; giuliomoro@0: blank.style.visibility = 'hidden'; giuliomoro@0: this.buttonPrevious.style.visibility = 'inherit'; giuliomoro@0: }; giuliomoro@0: giuliomoro@0: this.postNode = function() { giuliomoro@0: // This will take the node from the popupOptions and display it giuliomoro@0: var node = this.popupOptions[this.currentIndex]; giuliomoro@0: this.popupResponse.innerHTML = null; giuliomoro@0: this.popupTitle.textContent = node.specification.statement; giuliomoro@0: if (node.specification.type == 'question') { giuliomoro@0: var textArea = document.createElement('textarea'); giuliomoro@0: switch (node.specification.boxsize) { giuliomoro@0: case 'small': giuliomoro@0: textArea.cols = "20"; giuliomoro@0: textArea.rows = "1"; giuliomoro@0: break; giuliomoro@0: case 'normal': giuliomoro@0: textArea.cols = "30"; giuliomoro@0: textArea.rows = "2"; giuliomoro@0: break; giuliomoro@0: case 'large': giuliomoro@0: textArea.cols = "40"; giuliomoro@0: textArea.rows = "5"; giuliomoro@0: break; giuliomoro@0: case 'huge': giuliomoro@0: textArea.cols = "50"; giuliomoro@0: textArea.rows = "10"; giuliomoro@0: break; giuliomoro@0: } giuliomoro@0: if (node.response == undefined) { giuliomoro@0: node.response = ""; giuliomoro@0: } else { giuliomoro@0: textArea.value = node.response; giuliomoro@0: } giuliomoro@0: this.popupResponse.appendChild(textArea); giuliomoro@0: textArea.focus(); giuliomoro@0: this.popupResponse.style.textAlign="center"; giuliomoro@0: this.popupResponse.style.left="0%"; giuliomoro@0: } else if (node.specification.type == 'checkbox') { giuliomoro@0: if (node.response == undefined) { giuliomoro@0: node.response = Array(node.specification.options.length); giuliomoro@0: } giuliomoro@0: var index = 0; giuliomoro@0: var max_w = 0; giuliomoro@0: for (var option of node.specification.options) { giuliomoro@0: var input = document.createElement('input'); giuliomoro@0: input.id = option.name; giuliomoro@0: input.type = 'checkbox'; giuliomoro@0: var span = document.createElement('span'); giuliomoro@0: span.textContent = option.text; giuliomoro@0: var hold = document.createElement('div'); giuliomoro@0: hold.setAttribute('name','option'); giuliomoro@0: hold.style.padding = '4px'; giuliomoro@0: hold.appendChild(input); giuliomoro@0: hold.appendChild(span); giuliomoro@0: this.popupResponse.appendChild(hold); giuliomoro@0: if (node.response[index] != undefined){ giuliomoro@0: if (node.response[index].checked == true) { giuliomoro@0: input.checked = "true"; giuliomoro@0: } giuliomoro@0: } giuliomoro@0: var w = $(span).width(); giuliomoro@0: if (w > max_w) giuliomoro@0: max_w = w; giuliomoro@0: index++; giuliomoro@0: } giuliomoro@0: max_w += 12; giuliomoro@0: this.popupResponse.style.textAlign=""; giuliomoro@0: var leftP = ((max_w/500)/2)*100; giuliomoro@0: this.popupResponse.style.left=leftP+"%"; giuliomoro@0: } else if (node.specification.type == 'radio') { giuliomoro@0: if (node.response == undefined) { giuliomoro@0: node.response = {name: "", text: ""}; giuliomoro@0: } giuliomoro@0: var index = 0; giuliomoro@0: var max_w = 0; giuliomoro@0: for (var option of node.specification.options) { giuliomoro@0: var input = document.createElement('input'); giuliomoro@0: input.id = option.name; giuliomoro@0: input.type = 'radio'; giuliomoro@0: input.name = node.specification.id; giuliomoro@0: var span = document.createElement('span'); giuliomoro@0: span.textContent = option.text; giuliomoro@0: var hold = document.createElement('div'); giuliomoro@0: hold.setAttribute('name','option'); giuliomoro@0: hold.style.padding = '4px'; giuliomoro@0: hold.appendChild(input); giuliomoro@0: hold.appendChild(span); giuliomoro@0: this.popupResponse.appendChild(hold); giuliomoro@0: if (input.id == node.response.name) { giuliomoro@0: input.checked = "true"; giuliomoro@0: } giuliomoro@0: var w = $(span).width(); giuliomoro@0: if (w > max_w) giuliomoro@0: max_w = w; giuliomoro@0: } giuliomoro@0: max_w += 12; giuliomoro@0: this.popupResponse.style.textAlign=""; giuliomoro@0: var leftP = ((max_w/500)/2)*100; giuliomoro@0: this.popupResponse.style.left=leftP+"%"; giuliomoro@0: } else if (node.specification.type == 'number') { giuliomoro@0: var input = document.createElement('input'); giuliomoro@0: input.type = 'textarea'; giuliomoro@0: if (node.min != null) {input.min = node.specification.min;} giuliomoro@0: if (node.max != null) {input.max = node.specification.max;} giuliomoro@0: if (node.step != null) {input.step = node.specification.step;} giuliomoro@0: if (node.response != undefined) { giuliomoro@0: input.value = node.response; giuliomoro@0: } giuliomoro@0: this.popupResponse.appendChild(input); giuliomoro@0: this.popupResponse.style.textAlign="center"; giuliomoro@0: this.popupResponse.style.left="0%"; giuliomoro@0: } giuliomoro@0: if(this.currentIndex+1 == this.popupOptions.length) { giuliomoro@0: if (this.node.location == "pre") { giuliomoro@0: this.buttonProceed.textContent = 'Start'; giuliomoro@0: } else { giuliomoro@0: this.buttonProceed.textContent = 'Submit'; giuliomoro@0: } giuliomoro@0: } else { giuliomoro@0: this.buttonProceed.textContent = 'Next'; giuliomoro@0: } giuliomoro@0: if(this.currentIndex > 0) giuliomoro@0: this.buttonPrevious.style.visibility = 'visible'; giuliomoro@0: else giuliomoro@0: this.buttonPrevious.style.visibility = 'hidden'; giuliomoro@0: }; giuliomoro@0: giuliomoro@0: this.initState = function(node,store) { giuliomoro@0: //Call this with your preTest and postTest nodes when needed to giuliomoro@0: // initialise the popup procedure. giuliomoro@0: if (node.options.length > 0) { giuliomoro@0: this.popupOptions = []; giuliomoro@0: this.node = node; giuliomoro@0: this.store = store; giuliomoro@0: for (var opt of node.options) giuliomoro@0: { giuliomoro@0: this.popupOptions.push({ giuliomoro@0: specification: opt, giuliomoro@0: response: null giuliomoro@0: }); giuliomoro@0: } giuliomoro@0: this.currentIndex = 0; giuliomoro@0: this.showPopup(); giuliomoro@0: this.postNode(); giuliomoro@0: } else { giuliomoro@0: advanceState(); giuliomoro@0: } giuliomoro@0: }; giuliomoro@0: giuliomoro@0: this.proceedClicked = function() { giuliomoro@0: // Each time the popup button is clicked! giuliomoro@0: var node = this.popupOptions[this.currentIndex]; giuliomoro@0: if (node.specification.type == 'question') { giuliomoro@0: // Must extract the question data giuliomoro@0: var textArea = $(popup.popupContent).find('textarea')[0]; giuliomoro@0: if (node.specification.mandatory == true && textArea.value.length == 0) { giuliomoro@0: alert('This question is mandatory'); giuliomoro@0: return; giuliomoro@0: } else { giuliomoro@0: // Save the text content giuliomoro@0: console.log("Question: "+ node.specification.statement); giuliomoro@0: console.log("Question Response: "+ textArea.value); giuliomoro@0: node.response = textArea.value; giuliomoro@0: } giuliomoro@0: } else if (node.specification.type == 'checkbox') { giuliomoro@0: // Must extract checkbox data giuliomoro@0: console.log("Checkbox: "+ node.specification.statement); giuliomoro@0: var inputs = this.popupResponse.getElementsByTagName('input'); giuliomoro@0: node.response = []; giuliomoro@0: for (var i=0; i node.max && node.max != null) { giuliomoro@0: alert('Number is above the maximum value of '+node.max); giuliomoro@0: return; giuliomoro@0: } giuliomoro@0: node.response = input.value; giuliomoro@0: } giuliomoro@0: this.currentIndex++; giuliomoro@0: if (this.currentIndex < this.popupOptions.length) { giuliomoro@0: this.postNode(); giuliomoro@0: } else { giuliomoro@0: // Reached the end of the popupOptions giuliomoro@0: this.hidePopup(); giuliomoro@0: for (var node of this.popupOptions) giuliomoro@0: { giuliomoro@0: this.store.postResult(node); giuliomoro@0: } giuliomoro@0: this.store.complete(); giuliomoro@0: advanceState(); giuliomoro@0: } giuliomoro@0: }; giuliomoro@0: giuliomoro@0: this.previousClick = function() { giuliomoro@0: // Triggered when the 'Back' button is clicked in the survey giuliomoro@0: if (this.currentIndex > 0) { giuliomoro@0: this.currentIndex--; giuliomoro@0: this.postNode(); giuliomoro@0: } giuliomoro@0: }; giuliomoro@0: giuliomoro@0: this.resize = function(event) giuliomoro@0: { giuliomoro@0: // Called on window resize; giuliomoro@0: if (this.popup != null) { giuliomoro@0: this.popup.style.left = (window.innerWidth/2)-250 + 'px'; giuliomoro@0: this.popup.style.top = (window.innerHeight/2)-125 + 'px'; giuliomoro@0: var blank = document.getElementsByClassName('testHalt')[0]; giuliomoro@0: blank.style.width = window.innerWidth; giuliomoro@0: blank.style.height = window.innerHeight; giuliomoro@0: } giuliomoro@0: }; giuliomoro@0: this.hideNextButton = function() { giuliomoro@0: this.buttonProceed.style.visibility = "hidden"; giuliomoro@0: } giuliomoro@0: this.hidePreviousButton = function() { giuliomoro@0: this.buttonPrevious.style.visibility = "hidden"; giuliomoro@0: } giuliomoro@0: this.showNextButton = function() { giuliomoro@0: this.buttonProceed.style.visibility = "visible"; giuliomoro@0: } giuliomoro@0: this.showPreviousButton = function() { giuliomoro@0: this.buttonPrevious.style.visibility = "visible"; giuliomoro@0: } giuliomoro@0: } giuliomoro@0: giuliomoro@0: function advanceState() giuliomoro@0: { giuliomoro@0: // Just for complete clarity giuliomoro@0: testState.advanceState(); giuliomoro@0: } giuliomoro@0: giuliomoro@0: function stateMachine() giuliomoro@0: { giuliomoro@0: // Object prototype for tracking and managing the test state giuliomoro@0: this.stateMap = []; giuliomoro@0: this.preTestSurvey = null; giuliomoro@0: this.postTestSurvey = null; giuliomoro@0: this.stateIndex = null; giuliomoro@0: this.currentStateMap = null; giuliomoro@0: this.currentStatePosition = null; giuliomoro@0: this.currentStore = null; giuliomoro@0: this.initialise = function(){ giuliomoro@0: giuliomoro@0: // Get the data from Specification giuliomoro@0: var pageHolder = []; giuliomoro@0: for (var page of specification.pages) giuliomoro@0: { giuliomoro@0: var repeat = page.repeatCount; giuliomoro@0: while(repeat >= 0) giuliomoro@0: { giuliomoro@0: pageHolder.push(page); giuliomoro@0: repeat--; giuliomoro@0: } giuliomoro@0: } giuliomoro@0: if (specification.randomiseOrder) giuliomoro@0: { giuliomoro@0: pageHolder = randomiseOrder(pageHolder); giuliomoro@0: } giuliomoro@0: for (var i=0; i 0) { giuliomoro@0: if(this.stateIndex != null) { giuliomoro@0: console.log('NOTE - State already initialise'); giuliomoro@0: } giuliomoro@0: this.stateIndex = -1; giuliomoro@0: } else { giuliomoro@0: console.log('FATAL - StateMap not correctly constructed. EMPTY_STATE_MAP'); giuliomoro@0: } giuliomoro@0: }; giuliomoro@0: this.advanceState = function(){ giuliomoro@0: if (this.stateIndex == null) { giuliomoro@0: this.initialise(); giuliomoro@0: } giuliomoro@1: $('#box-holders').fadeTo(200, 0.1, function(){ giuliomoro@1: $('#box-holders').fadeTo(200, 1); giuliomoro@1: }); giuliomoro@1: giuliomoro@1: giuliomoro@1: storage.update(); giuliomoro@0: if (this.stateIndex == -1) { giuliomoro@0: this.stateIndex++; giuliomoro@0: console.log('Starting test...'); giuliomoro@0: if (this.preTestSurvey != null) giuliomoro@0: { giuliomoro@0: popup.initState(this.preTestSurvey,storage.globalPreTest); giuliomoro@0: } else { giuliomoro@0: this.advanceState(); giuliomoro@0: } giuliomoro@0: } else if (this.stateIndex == this.stateMap.length) giuliomoro@0: { giuliomoro@0: // All test pages complete, post test giuliomoro@0: console.log('Ending test ...'); giuliomoro@0: this.stateIndex++; giuliomoro@0: if (this.postTestSurvey == null) { giuliomoro@0: this.advanceState(); giuliomoro@0: } else { giuliomoro@0: popup.initState(this.postTestSurvey,storage.globalPostTest); giuliomoro@0: } giuliomoro@0: } else if (this.stateIndex > this.stateMap.length) giuliomoro@0: { giuliomoro@0: createProjectSave(specification.projectReturn); giuliomoro@0: } giuliomoro@0: else giuliomoro@0: { giuliomoro@0: if (this.currentStateMap == null) giuliomoro@0: { giuliomoro@0: this.currentStateMap = this.stateMap[this.stateIndex]; giuliomoro@0: if (this.currentStateMap.randomiseOrder) giuliomoro@0: { giuliomoro@0: this.currentStateMap.audioElements = randomiseOrder(this.currentStateMap.audioElements); giuliomoro@0: } giuliomoro@0: this.currentStore = storage.testPages[this.stateIndex]; giuliomoro@0: if (this.currentStateMap.preTest != null) giuliomoro@0: { giuliomoro@0: this.currentStatePosition = 'pre'; giuliomoro@0: popup.initState(this.currentStateMap.preTest,storage.testPages[this.stateIndex].preTest); giuliomoro@0: } else { giuliomoro@0: this.currentStatePosition = 'test'; giuliomoro@0: } giuliomoro@0: interfaceContext.newPage(this.currentStateMap,storage.testPages[this.stateIndex]); giuliomoro@0: return; giuliomoro@0: } giuliomoro@0: switch(this.currentStatePosition) giuliomoro@0: { giuliomoro@0: case 'pre': giuliomoro@0: this.currentStatePosition = 'test'; giuliomoro@0: break; giuliomoro@0: case 'test': giuliomoro@0: this.currentStatePosition = 'post'; giuliomoro@0: // Save the data giuliomoro@0: this.testPageCompleted(); giuliomoro@0: if (this.currentStateMap.postTest == null) giuliomoro@0: { giuliomoro@0: this.advanceState(); giuliomoro@0: return; giuliomoro@0: } else { giuliomoro@0: popup.initState(this.currentStateMap.postTest,storage.testPages[this.stateIndex].postTest); giuliomoro@0: } giuliomoro@0: break; giuliomoro@0: case 'post': giuliomoro@0: this.stateIndex++; giuliomoro@0: this.currentStateMap = null; giuliomoro@0: this.advanceState(); giuliomoro@0: break; giuliomoro@0: }; giuliomoro@0: } giuliomoro@0: }; giuliomoro@0: giuliomoro@0: this.testPageCompleted = function() { giuliomoro@0: // Function called each time a test page has been completed giuliomoro@0: var storePoint = storage.testPages[this.stateIndex]; giuliomoro@0: // First get the test metric giuliomoro@0: giuliomoro@0: var metric = storePoint.XMLDOM.getElementsByTagName('metric')[0]; giuliomoro@0: if (audioEngineContext.metric.enableTestTimer) giuliomoro@0: { giuliomoro@0: var testTime = storePoint.parent.document.createElement('metricresult'); giuliomoro@0: testTime.id = 'testTime'; giuliomoro@0: testTime.textContent = audioEngineContext.timer.testDuration; giuliomoro@0: metric.appendChild(testTime); giuliomoro@0: } giuliomoro@0: giuliomoro@0: var audioObjects = audioEngineContext.audioObjects; giuliomoro@0: for (var ao of audioEngineContext.audioObjects) giuliomoro@0: { giuliomoro@0: ao.exportXMLDOM(); giuliomoro@0: } giuliomoro@0: for (var element of interfaceContext.commentQuestions) giuliomoro@0: { giuliomoro@0: element.exportXMLDOM(storePoint); giuliomoro@0: } giuliomoro@0: pageXMLSave(storePoint.XMLDOM, this.currentStateMap); giuliomoro@0: storePoint.complete(); giuliomoro@0: }; giuliomoro@0: } giuliomoro@0: giuliomoro@0: function AudioEngine(specification) { giuliomoro@0: giuliomoro@0: // Create two output paths, the main outputGain and fooGain. giuliomoro@0: // Output gain is default to 1 and any items for playback route here giuliomoro@0: // Foo gain is used for analysis to ensure paths get processed, but are not heard giuliomoro@0: // because web audio will optimise and any route which does not go to the destination gets ignored. giuliomoro@0: this.outputGain = audioContext.createGain(); giuliomoro@0: this.fooGain = audioContext.createGain(); giuliomoro@0: this.fooGain.gain = 0; giuliomoro@0: giuliomoro@0: // Use this to detect playback state: 0 - stopped, 1 - playing giuliomoro@0: this.status = 0; giuliomoro@0: giuliomoro@0: // Connect both gains to output giuliomoro@0: this.outputGain.connect(audioContext.destination); giuliomoro@0: this.fooGain.connect(audioContext.destination); giuliomoro@0: giuliomoro@0: // Create the timer Object giuliomoro@0: this.timer = new timer(); giuliomoro@0: // Create session metrics giuliomoro@0: this.metric = new sessionMetrics(this,specification); giuliomoro@0: giuliomoro@0: this.loopPlayback = false; giuliomoro@0: giuliomoro@0: this.pageStore = null; giuliomoro@0: giuliomoro@0: // Create store for new audioObjects giuliomoro@0: this.audioObjects = []; giuliomoro@0: giuliomoro@0: this.buffers = []; giuliomoro@0: this.bufferObj = function() giuliomoro@0: { giuliomoro@0: this.url = null; giuliomoro@0: this.buffer = null; giuliomoro@0: this.xmlRequest = new XMLHttpRequest(); giuliomoro@0: this.xmlRequest.parent = this; giuliomoro@0: this.users = []; giuliomoro@0: this.progress = 0; giuliomoro@0: this.status = 0; giuliomoro@0: this.ready = function() giuliomoro@0: { giuliomoro@0: if (this.status >= 2) giuliomoro@0: { giuliomoro@0: this.status = 3; giuliomoro@0: } giuliomoro@0: for (var i=0; i 0) {this.wasMoved = true;} giuliomoro@0: this.movementTracker[this.movementTracker.length] = [time, position]; giuliomoro@0: }; giuliomoro@0: giuliomoro@0: this.startListening = function(time) giuliomoro@0: { giuliomoro@0: if (this.listenHold == false) giuliomoro@0: { giuliomoro@0: this.wasListenedTo = true; giuliomoro@0: this.listenStart = time; giuliomoro@0: this.listenHold = true; giuliomoro@0: giuliomoro@0: var evnt = document.createElement('event'); giuliomoro@0: var testTime = document.createElement('testTime'); giuliomoro@0: testTime.setAttribute('start',time); giuliomoro@0: var bufferTime = document.createElement('bufferTime'); giuliomoro@0: bufferTime.setAttribute('start',this.parent.getCurrentPosition()); giuliomoro@0: evnt.appendChild(testTime); giuliomoro@0: evnt.appendChild(bufferTime); giuliomoro@0: this.listenTracker.push(evnt); giuliomoro@0: giuliomoro@0: console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id giuliomoro@0: } giuliomoro@0: }; giuliomoro@0: giuliomoro@0: this.stopListening = function(time,bufferStopTime) giuliomoro@0: { giuliomoro@0: if (this.listenHold == true) giuliomoro@0: { giuliomoro@0: var diff = time - this.listenStart; giuliomoro@0: this.listenedTimer += (diff); giuliomoro@0: this.listenStart = 0; giuliomoro@0: this.listenHold = false; giuliomoro@0: giuliomoro@0: var evnt = this.listenTracker[this.listenTracker.length-1]; giuliomoro@0: var testTime = evnt.getElementsByTagName('testTime')[0]; giuliomoro@0: var bufferTime = evnt.getElementsByTagName('bufferTime')[0]; giuliomoro@0: testTime.setAttribute('stop',time); giuliomoro@0: if (bufferStopTime == undefined) { giuliomoro@0: bufferTime.setAttribute('stop',this.parent.getCurrentPosition()); giuliomoro@0: } else { giuliomoro@0: bufferTime.setAttribute('stop',bufferStopTime); giuliomoro@0: } giuliomoro@0: console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id giuliomoro@0: } giuliomoro@0: }; giuliomoro@0: giuliomoro@0: this.exportXMLDOM = function() { giuliomoro@0: var storeDOM = []; giuliomoro@0: if (audioEngineContext.metric.enableElementTimer) { giuliomoro@0: var mElementTimer = storage.document.createElement('metricresult'); giuliomoro@0: mElementTimer.setAttribute('name','enableElementTimer'); giuliomoro@0: mElementTimer.textContent = this.listenedTimer; giuliomoro@0: storeDOM.push(mElementTimer); giuliomoro@0: } giuliomoro@0: if (audioEngineContext.metric.enableElementTracker) { giuliomoro@0: var elementTrackerFull = storage.document.createElement('metricResult'); giuliomoro@0: elementTrackerFull.setAttribute('name','elementTrackerFull'); giuliomoro@0: for (var k=0; k giuliomoro@0: // DD/MM/YY giuliomoro@0: // giuliomoro@0: // giuliomoro@0: var dateTime = new Date(); giuliomoro@0: var year = document.createAttribute('year'); giuliomoro@0: var month = document.createAttribute('month'); giuliomoro@0: var day = document.createAttribute('day'); giuliomoro@0: var hour = document.createAttribute('hour'); giuliomoro@0: var minute = document.createAttribute('minute'); giuliomoro@0: var secs = document.createAttribute('secs'); giuliomoro@0: giuliomoro@0: year.nodeValue = dateTime.getFullYear(); giuliomoro@0: month.nodeValue = dateTime.getMonth()+1; giuliomoro@0: day.nodeValue = dateTime.getDate(); giuliomoro@0: hour.nodeValue = dateTime.getHours(); giuliomoro@0: minute.nodeValue = dateTime.getMinutes(); giuliomoro@0: secs.nodeValue = dateTime.getSeconds(); giuliomoro@0: giuliomoro@0: var hold = document.createElement("datetime"); giuliomoro@0: var date = document.createElement("date"); giuliomoro@0: date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue; giuliomoro@0: var time = document.createElement("time"); giuliomoro@0: time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue; giuliomoro@0: giuliomoro@0: date.setAttributeNode(year); giuliomoro@0: date.setAttributeNode(month); giuliomoro@0: date.setAttributeNode(day); giuliomoro@0: time.setAttributeNode(hour); giuliomoro@0: time.setAttributeNode(minute); giuliomoro@0: time.setAttributeNode(secs); giuliomoro@0: giuliomoro@0: hold.appendChild(date); giuliomoro@0: hold.appendChild(time); giuliomoro@0: return hold; giuliomoro@0: giuliomoro@0: } giuliomoro@0: giuliomoro@0: function Specification() { giuliomoro@0: // Handles the decoding of the project specification XML into a simple JavaScript Object. giuliomoro@0: giuliomoro@0: this.interface = null; giuliomoro@0: this.projectReturn = "null"; giuliomoro@0: this.randomiseOrder = null; giuliomoro@0: this.testPages = null; giuliomoro@0: this.pages = []; giuliomoro@0: this.metrics = null; giuliomoro@0: this.interfaces = null; giuliomoro@0: this.loudness = null; giuliomoro@0: this.errors = []; giuliomoro@0: this.schema = null; giuliomoro@0: giuliomoro@0: this.processAttribute = function(attribute,schema) giuliomoro@0: { giuliomoro@0: // attribute is the string returned from getAttribute on the XML giuliomoro@0: // schema is the node giuliomoro@0: if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined) giuliomoro@0: { giuliomoro@0: schema = this.schema.getAllElementsByName(schema.getAttribute('ref'))[0]; giuliomoro@0: } giuliomoro@0: var defaultOpt = schema.getAttribute('default'); giuliomoro@0: if (attribute == null) { giuliomoro@0: attribute = defaultOpt; giuliomoro@0: } giuliomoro@0: var dataType = schema.getAttribute('type'); giuliomoro@0: if (typeof dataType == "string") { dataType = dataType.substr(3);} giuliomoro@0: else {dataType = "string";} giuliomoro@0: if (attribute == null) giuliomoro@0: { giuliomoro@0: return attribute; giuliomoro@0: } giuliomoro@0: switch(dataType) giuliomoro@0: { giuliomoro@0: case "boolean": giuliomoro@0: if (attribute == 'true'){attribute = true;}else{attribute=false;} giuliomoro@0: break; giuliomoro@0: case "negativeInteger": giuliomoro@0: case "positiveInteger": giuliomoro@0: case "nonNegativeInteger": giuliomoro@0: case "nonPositiveInteger": giuliomoro@0: case "integer": giuliomoro@0: case "decimal": giuliomoro@0: case "short": giuliomoro@0: attribute = Number(attribute); giuliomoro@0: break; giuliomoro@0: case "string": giuliomoro@0: default: giuliomoro@0: attribute = String(attribute); giuliomoro@0: break; giuliomoro@0: } giuliomoro@0: return attribute; giuliomoro@0: }; giuliomoro@0: giuliomoro@0: this.decode = function(projectXML) { giuliomoro@0: this.errors = []; giuliomoro@0: // projectXML - DOM Parsed document giuliomoro@0: this.projectXML = projectXML.childNodes[0]; giuliomoro@0: var setupNode = projectXML.getElementsByTagName('setup')[0]; giuliomoro@0: var schemaSetup = this.schema.getAllElementsByName('setup')[0]; giuliomoro@0: // First decode the attributes giuliomoro@0: var attributes = schemaSetup.getAllElementsByTagName('xs:attribute'); giuliomoro@0: for (var i in attributes) giuliomoro@0: { giuliomoro@0: if (isNaN(Number(i)) == true){break;} giuliomoro@0: var attributeName = attributes[i].getAttribute('name'); giuliomoro@0: var projectAttr = setupNode.getAttribute(attributeName); giuliomoro@0: projectAttr = this.processAttribute(projectAttr,attributes[i]); giuliomoro@0: switch(typeof projectAttr) giuliomoro@0: { giuliomoro@0: case "number": giuliomoro@0: case "boolean": giuliomoro@0: eval('this.'+attributeName+' = '+projectAttr); giuliomoro@0: break; giuliomoro@0: case "string": giuliomoro@0: eval('this.'+attributeName+' = "'+projectAttr+'"'); giuliomoro@0: break; giuliomoro@0: } giuliomoro@0: giuliomoro@0: } giuliomoro@0: giuliomoro@0: this.metrics = new this.metricNode(); giuliomoro@0: giuliomoro@0: this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]); giuliomoro@0: giuliomoro@0: // Now process the survey node options giuliomoro@0: var survey = setupNode.getElementsByTagName('survey'); giuliomoro@0: for (var i in survey) { giuliomoro@0: if (isNaN(Number(i)) == true){break;} giuliomoro@0: var location = survey[i].getAttribute('location'); giuliomoro@0: if (location == 'pre' || location == 'before') giuliomoro@0: { giuliomoro@0: if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");} giuliomoro@0: else { giuliomoro@0: this.preTest = new this.surveyNode(); giuliomoro@0: this.preTest.decode(this,survey[i]); giuliomoro@0: } giuliomoro@0: } else if (location == 'post' || location == 'after') { giuliomoro@0: if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");} giuliomoro@0: else { giuliomoro@0: this.postTest = new this.surveyNode(); giuliomoro@0: this.postTest.decode(this,survey[i]); giuliomoro@0: } giuliomoro@0: } giuliomoro@0: } giuliomoro@0: giuliomoro@0: var interfaceNode = setupNode.getElementsByTagName('interface'); giuliomoro@0: if (interfaceNode.length > 1) giuliomoro@0: { giuliomoro@0: this.errors.push("Only one node in the node allowed! Others except first ingnored!"); giuliomoro@0: } giuliomoro@0: this.interfaces = new this.interfaceNode(); giuliomoro@0: if (interfaceNode.length != 0) giuliomoro@0: { giuliomoro@0: interfaceNode = interfaceNode[0]; giuliomoro@0: this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]); giuliomoro@0: } giuliomoro@0: giuliomoro@0: // Page tags giuliomoro@0: var pageTags = projectXML.getElementsByTagName('page'); giuliomoro@0: var pageSchema = this.schema.getAllElementsByName('page')[0]; giuliomoro@0: for (var i=0; i giuliomoro@0: var commentboxprefix = root.createElement("commentboxprefix"); giuliomoro@0: commentboxprefix.textContent = this.commentBoxPrefix; giuliomoro@0: AHNode.appendChild(commentboxprefix); giuliomoro@0: giuliomoro@0: for (var i=0; i giuliomoro@0: for (var i=0; i