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