nickjillings@1341: // Once this is loaded and parsed, begin execution nickjillings@1341: loadInterface(); nicholas@2691: /*globals window, interfaceContext, testState, Interface, audioEngineContext, console, document, specification, $, storage*/ nickjillings@1341: function loadInterface() { nicholas@2538: // Get the dimensions of the screen available to the page nicholas@2538: var width = window.innerWidth; nicholas@2538: var height = window.innerHeight; nicholas@2538: interfaceContext.insertPoint.innerHTML = ""; // Clear the current schema nicholas@2538: nicholas@2538: // Custom comparator Object nicholas@2538: Interface.prototype.comparator = null; nicholas@2538: nicholas@2538: // The injection point into the HTML page nicholas@2538: interfaceContext.insertPoint = document.getElementById("topLevelBody"); nicholas@2538: var testContent = document.createElement('div'); nicholas@2538: testContent.id = 'testContent'; nicholas@2538: nicholas@2538: // Create the top div for the Title element nicholas@2538: var titleAttr = specification.title; nicholas@2538: var title = document.createElement('div'); nicholas@2538: title.className = "title"; nicholas@2538: title.align = "center"; nicholas@2538: var titleSpan = document.createElement('span'); nicholas@2470: titleSpan.id = "test-title"; nicholas@2538: nicholas@2538: // Set title to that defined in XML, else set to default nicholas@2691: if (titleAttr !== undefined) { nicholas@2538: titleSpan.textContent = titleAttr; nicholas@2538: } else { nicholas@2538: titleSpan.textContent = 'Listening test'; nicholas@2538: } nicholas@2538: // Insert the titleSpan element into the title div element. nicholas@2538: title.appendChild(titleSpan); nicholas@2538: nicholas@2538: var pagetitle = document.createElement('div'); nicholas@2538: pagetitle.className = "pageTitle"; nicholas@2538: pagetitle.align = "center"; nicholas@2691: nicholas@2691: titleSpan = document.createElement('span'); nicholas@2538: titleSpan.id = "pageTitle"; nicholas@2538: pagetitle.appendChild(titleSpan); nicholas@2538: nicholas@2538: // Create Interface buttons! nicholas@2538: var interfaceButtons = document.createElement('div'); nicholas@2538: interfaceButtons.id = 'interface-buttons'; nicholas@2538: interfaceButtons.style.height = '25px'; nicholas@2538: nicholas@2538: // Create playback start/stop points nicholas@2538: var playback = document.createElement("button"); nicholas@2538: playback.innerHTML = 'Stop'; nicholas@2538: playback.id = 'playback-button'; nicholas@2538: playback.style.float = 'left'; nicholas@2538: // onclick function. Check if it is playing or not, call the correct function in the nicholas@2538: // audioEngine, change the button text to reflect the next state. nicholas@2538: playback.onclick = function () { nicholas@2538: if (audioEngineContext.status == 1) { nicholas@2538: audioEngineContext.stop(); nicholas@2538: this.innerHTML = 'Stop'; nickjillings@1341: var time = audioEngineContext.timer.getTestTime(); nickjillings@1341: console.log('Stopped at ' + time); // DEBUG/SAFETY nicholas@2538: } nicholas@2538: }; nicholas@2538: // Append the interface buttons into the interfaceButtons object. nicholas@2538: interfaceButtons.appendChild(playback); nicholas@2538: nicholas@2538: // Global parent for the comment boxes on the page nicholas@2538: var feedbackHolder = document.createElement('div'); nicholas@2538: feedbackHolder.id = 'feedbackHolder'; nicholas@2538: nicholas@2396: // Create outside reference holder nicholas@2396: var outsideRef = document.createElement("div"); nicholas@2396: outsideRef.id = "outside-reference-holder"; nicholas@2538: nicholas@2538: // Construct the AB Boxes nicholas@2538: var boxes = document.createElement('div'); nicholas@2538: boxes.id = "box-holders"; nicholas@2538: nicholas@3047: var submitHolder = document.createElement("div"); n@3095: submitHolder.id = "submit-holder"; nicholas@2538: var submit = document.createElement('button'); nicholas@2538: submit.id = "submit"; nicholas@2538: submit.onclick = buttonSubmitClick; nicholas@2538: submit.className = "big-button"; nicholas@3047: submit.textContent = "Submit"; nicholas@3047: submitHolder.appendChild(submit); nicholas@2538: nicholas@2538: feedbackHolder.appendChild(boxes); nicholas@2538: nicholas@2475: // Create holder for comment boxes nicholas@2475: var comments = document.createElement("div"); nicholas@2475: comments.id = "comment-box-holder"; nicholas@2538: nicholas@2538: // Inject into HTML nicholas@2538: testContent.appendChild(title); // Insert the title nicholas@2538: testContent.appendChild(pagetitle); nicholas@2538: testContent.appendChild(interfaceButtons); nicholas@2396: testContent.appendChild(outsideRef); nicholas@2538: testContent.appendChild(feedbackHolder); nicholas@3047: testContent.appendChild(submitHolder); nicholas@2475: testContent.appendChild(comments); nicholas@2538: interfaceContext.insertPoint.appendChild(testContent); nickjillings@1341: nicholas@2538: // Load the full interface nicholas@2538: testState.initialise(); nicholas@2538: testState.advanceState(); nickjillings@1341: } nickjillings@1341: nicholas@2538: function loadTest(audioHolderObject) { nicholas@2538: var feedbackHolder = document.getElementById('feedbackHolder'); nicholas@2651: var interfaceObj = interfaceContext.getCombinedInterfaces(audioHolderObject); nicholas@2538: if (interfaceObj.length > 1) { nicholas@2538: console.log("WARNING - This interface only supports one node per page. Using first interface node"); nicholas@2538: } nicholas@2538: interfaceObj = interfaceObj[0]; nicholas@2538: nicholas@2475: var commentHolder = document.getElementById('comment-box-holder'); nicholas@2475: commentHolder.innerHTML = ""; nicholas@2538: nicholas@2396: // Delete outside reference nicholas@2538: var outsideReferenceHolder = document.getElementById("outside-reference-holder"); nicholas@2396: outsideReferenceHolder.innerHTML = ""; nicholas@2538: nicholas@2470: // Set the page title nicholas@2470: if (typeof audioHolderObject.title == "string" && audioHolderObject.title.length > 0) { nicholas@2691: document.getElementById("test-title").textContent = audioHolderObject.title; nicholas@2470: } nicholas@2538: nicholas@2779: if (interfaceObj.title !== undefined) { nicholas@2538: document.getElementById("pageTitle").textContent = interfaceObj.title; nicholas@2538: } nicholas@2538: nicholas@2779: if (interfaceObj.image !== undefined) { nicholas@2779: feedbackHolder.insertBefore(interfaceContext.imageHolder.root, document.getElementById("box-holders")); nicholas@2779: interfaceContext.imageHolder.setImage(interfaceObj.image); nicholas@2779: } nicholas@2779: nicholas@2651: var interfaceOptions = interfaceObj.options; nicholas@2394: // Clear the interfaceElements nicholas@2394: { nicholas@2394: var node = document.getElementById('playback-holder'); nicholas@2538: if (node) { nicholas@2538: feedbackHolder.removeChild(node); nicholas@2538: } nicholas@2394: node = document.getElementById('page-count'); nicholas@2538: if (node) { nicholas@2538: document.getElementById('interface-buttons').removeChild(node); nicholas@2538: } nicholas@2394: node = document.getElementById('master-volume-holder-float'); nicholas@2538: if (node) { nicholas@2538: feedbackHolder.removeChild(node); nicholas@2538: } nicholas@2394: } nicholas@2538: n@2407: // Populate the comparator object nicholas@2538: interfaceContext.comparator = new comparator(audioHolderObject); nicholas@2538: nicholas@2538: for (var option of interfaceOptions) { nicholas@2538: if (option.type == "show") { nicholas@2538: switch (option.name) { nickjillings@1356: case "playhead": nickjillings@1356: var playbackHolder = document.getElementById('playback-holder'); nicholas@2691: if (playbackHolder === null) { nickjillings@1356: playbackHolder = document.createElement('div'); nicholas@2394: playbackHolder.id = 'playback-holder'; nickjillings@1356: playbackHolder.style.width = "100%"; nickjillings@1356: playbackHolder.style.float = "left"; nickjillings@1356: playbackHolder.align = 'center'; nickjillings@1356: playbackHolder.appendChild(interfaceContext.playhead.object); nickjillings@1356: feedbackHolder.appendChild(playbackHolder); nickjillings@1356: } nickjillings@1356: break; nickjillings@1356: case "page-count": nickjillings@1356: var pagecountHolder = document.getElementById('page-count'); nicholas@2691: if (pagecountHolder === null) { nickjillings@1356: pagecountHolder = document.createElement('div'); nickjillings@1356: pagecountHolder.id = 'page-count'; nicholas@2393: document.getElementById('interface-buttons').appendChild(pagecountHolder); nickjillings@1356: } nicholas@2538: pagecountHolder.innerHTML = 'Page ' + (testState.stateIndex + 1) + ' of ' + testState.stateMap.length + ''; nickjillings@1356: break; nickjillings@1356: case "volume": nicholas@2691: if (document.getElementById('master-volume-holder-float') === null) { nickjillings@1356: feedbackHolder.appendChild(interfaceContext.volume.object); nickjillings@1356: } nickjillings@1356: break; n@2407: case "comments": n@2407: // Generate one comment box per presented page nicholas@2538: for (var element of audioEngineContext.audioObjects) { n@2407: interfaceContext.commentBoxes.createCommentBox(element); n@2407: } nicholas@2538: interfaceContext.commentBoxes.showCommentBoxes(commentHolder, true); n@2407: break; nickjillings@1356: } nickjillings@1356: } nickjillings@1356: } nicholas@2538: nicholas@2538: $(audioHolderObject.commentQuestions).each(function (index, element) { nicholas@2538: var node = interfaceContext.createCommentQuestion(element); nicholas@2538: commentHolder.appendChild(node.holder); nicholas@2538: }); nicholas@2538: nicholas@2538: resizeWindow(null); nickjillings@1341: } nickjillings@1341: nicholas@2538: function comparator(audioHolderObject) { nicholas@2538: this.comparatorBox = function (audioElement, id, text) { nicholas@2538: this.parent = audioElement; nicholas@2538: this.id = id; nicholas@2538: this.value = 0; nicholas@2538: this.disabled = true; nicholas@2538: this.box = document.createElement('div'); nicholas@2538: this.box.className = 'comparator-holder'; nicholas@2538: this.box.setAttribute('track-id', audioElement.id); nicholas@2538: this.box.id = 'comparator-' + text; nicholas@2538: this.selector = document.createElement('div'); nicholas@2538: this.selector.className = 'comparator-selector disabled'; n@2794: if (audioElement.specification.image) { n@2794: this.selector.className += " comparator-image"; n@2794: var image = document.createElement("img"); n@2794: image.src = audioElement.specification.image; n@2794: image.className = "comparator-image"; n@2794: this.selector.appendChild(image); n@2794: } nicholas@2538: var selectorText = document.createElement('span'); nicholas@2538: selectorText.textContent = text; nicholas@2538: this.selector.appendChild(selectorText); nicholas@2538: this.playback = document.createElement('button'); nicholas@2538: this.playback.className = 'comparator-button'; nicholas@2538: this.playback.disabled = true; nicholas@2538: this.playback.textContent = "Listen"; nicholas@2538: this.box.appendChild(this.selector); nicholas@2538: this.box.appendChild(this.playback); nicholas@2691: this.selectorClicked = function () { nicholas@2691: var i; nicholas@2538: var time = audioEngineContext.timer.getTestTime(); nicholas@2691: if (this.parent.state !== 1) { nicholas@2691: interfaceContext.lightbox.post("Message", "Please wait for the sample to load"); nicholas@2538: console.log("Please wait until sample has loaded"); nicholas@2538: return; nickjillings@2112: } nicholas@2691: if (audioEngineContext.status === 0) { nicholas@2538: interfaceContext.lightbox.post("Message", "Please listen to the samples before making a selection"); nicholas@2538: console.log("Please listen to the samples before making a selection"); nicholas@2538: return; nicholas@2538: } nicholas@2691: interfaceContext.comparator.selected = this.id; nicholas@2691: $(".comparator-selector").removeClass('selected'); nicholas@2691: $(this.selector).addClass('selected'); nicholas@2691: this.comparator.comparators.forEach(function (a) { nicholas@2691: if (a !== this) { nicholas@2691: a.value = 0; nicholas@2691: } else { nicholas@2691: a.value = 1; nickjillings@2112: } nicholas@2693: a.parent.metric.moved(time, a.value); nicholas@2691: }, this); nicholas@2691: console.log("Selected " + this.id + ' (' + time + ')'); nicholas@2538: }; nicholas@2538: this.playback.setAttribute("playstate", "ready"); nicholas@2691: this.playbackClicked = function () { n@2694: if (this.playback.getAttribute("playstate") == "ready") { nicholas@2691: audioEngineContext.play(this.id); n@2694: } else if (this.playback.getAttribute("playstate") == "playing") { nickjillings@1376: audioEngineContext.stop(); nickjillings@1376: } nicholas@2538: nicholas@2538: }; nicholas@2691: this.handleEvent = function (event) { nicholas@2691: if (event.currentTarget === this.selector) { nicholas@2691: this.selectorClicked(); nicholas@2691: } else if (event.currentTarget === this.playback) { nicholas@2691: this.playbackClicked(); nicholas@2691: } n@2794: }; nicholas@2691: this.playback.addEventListener("click", this); nicholas@2691: this.selector.addEventListener("click", this); nicholas@2538: nicholas@2538: this.enable = function () { nicholas@2538: if (this.parent.state == 1) { nicholas@2538: $(this.selector).removeClass('disabled'); nicholas@2538: this.playback.disabled = false; nicholas@2538: } nicholas@2538: }; nicholas@2538: this.updateLoading = function (progress) { nicholas@2538: if (progress != 100) { nicholas@2538: progress = String(progress); nicholas@2538: progress = progress.split('.')[0]; nicholas@2538: this.playback.textContent = progress + '%'; nicholas@2538: } else { nicholas@2538: this.playback.textContent = "Play"; nicholas@2538: } nicholas@2538: }; nicholas@2538: this.error = function () { nickjillings@2113: // audioObject has an error!! nickjillings@2113: this.playback.textContent = "Error"; nickjillings@2113: $(this.playback).addClass("error-colour"); nicholas@2691: }; nicholas@2538: this.startPlayback = function () { n@2426: if (this.parent.specification.parent.playOne || specification.playOne) { n@2426: $('.comparator-button').text('Wait'); nicholas@2538: $('.comparator-button').attr("disabled", "true"); nicholas@2732: $(this.playback).removeAttr("disabled"); n@2426: } else { n@2426: $('.comparator-button').text('Listen'); n@2426: } nickjillings@1376: $(this.playback).text('Stop'); nicholas@2538: this.playback.setAttribute("playstate", "playing"); nicholas@2726: interfaceContext.commentBoxes.highlightById(audioElement.id); nickjillings@1360: }; nicholas@2538: this.stopPlayback = function () { n@2426: if (this.playback.getAttribute("playstate") == "playing") { nicholas@2732: $(this.playback).text('Listen'); nicholas@2732: $(this.playback).removeAttr("disabled"); nicholas@2538: this.playback.setAttribute("playstate", "ready"); nicholas@2732: if (this.parent.specification.parent.playOne || specification.playOne) { nicholas@2732: $('.comparator-button').text('Listen'); nicholas@2732: $('.comparator-button').removeAttr("disabled"); nicholas@2732: } n@2426: } nicholas@2726: var box = interfaceContext.commentBoxes.boxes.find(function (a) { nicholas@2726: return a.id === audioElement.id; nicholas@2726: }); nicholas@2726: if (box) { nicholas@2726: box.highlight(false); nicholas@2726: } nickjillings@1360: }; nicholas@2538: this.exportXMLDOM = function (audioObject) { nicholas@2538: var node = storage.document.createElement('value'); nicholas@2538: node.textContent = this.value; nicholas@3138: var iname = testState.getCurrentTestPage().interfaces[0].name; nicholas@3138: if (typeof iname == "string") { nicholas@3138: node.setAttribute("interface-name", iname); nicholas@3138: } nicholas@2538: return node; nicholas@2538: }; nicholas@2538: this.getValue = function () { nicholas@2538: return this.value; nicholas@2538: }; nicholas@2538: this.getPresentedId = function () { nicholas@2538: return this.selector.children[0].textContent; nicholas@2538: }; nicholas@2538: this.canMove = function () { nicholas@2538: return false; nicholas@2538: }; nicholas@2538: }; nicholas@2538: nicholas@2538: this.boxHolders = document.getElementById('box-holders'); nicholas@2538: this.boxHolders.innerHTML = ""; nicholas@2538: this.comparators = []; nicholas@2538: this.selected = null; nicholas@2538: nicholas@2607: var labelType = audioHolderObject.label; nicholas@2607: if (labelType == "default") { nicholas@2607: labelType = "capital"; nicholas@2607: } nicholas@2607: nicholas@2538: // First generate the Audio Objects for the Audio Engine nicholas@2538: for (var index = 0; index < audioHolderObject.audioElements.length; index++) { nicholas@2538: var element = audioHolderObject.audioElements[index]; nickjillings@2177: var audioObject = audioEngineContext.newTrack(element); nicholas@2538: if (index == audioHolderObject.outsideReference || element.type == 'outside-reference') { nicholas@2538: var orNode = new interfaceContext.outsideReferenceDOM(audioObject, index, document.getElementById("outside-reference-holder")); nicholas@2538: audioObject.bindInterface(orNode); nickjillings@2177: } else { nicholas@2663: var label = element.label; nicholas@2663: if (label === "") { nicholas@2663: label = interfaceContext.getLabel(labelType, index, audioHolderObject.labelStart); nicholas@2663: } nicholas@2538: var node = new this.comparatorBox(audioObject, index, label); nicholas@2691: Object.defineProperties(node, { nicholas@2691: 'comparator': { nicholas@2691: 'value': this nicholas@2691: } nicholas@2691: }); nickjillings@2177: audioObject.bindInterface(node); nickjillings@2177: this.comparators.push(node); nickjillings@2177: this.boxHolders.appendChild(node.box); nickjillings@1295: } nicholas@2538: } nicholas@2538: return this; nickjillings@1341: } nickjillings@1341: nicholas@2538: function resizeWindow(event) { nicholas@2538: document.getElementById('submit').style.left = (window.innerWidth - 250) / 2 + 'px'; nicholas@2538: var numObj = interfaceContext.comparator.comparators.length; nicholas@2538: var boxW = numObj * 312; nickjillings@1341: var diff = window.innerWidth - boxW; nicholas@2538: while (diff < 0) { nicholas@2538: numObj = Math.ceil(numObj / 2); nicholas@2538: boxW = numObj * 312; nickjillings@1341: diff = window.innerWidth - boxW; nickjillings@1341: } nicholas@2538: nickjillings@2177: var outsideRef = document.getElementById('outside-reference'); nicholas@2691: if (outsideRef !== null) { nicholas@2538: outsideRef.style.left = (window.innerWidth - 120) / 2 + 'px'; nicholas@2538: } nickjillings@1341: } nickjillings@1341: nicholas@2538: function buttonSubmitClick() { nicholas@2651: var checks = testState.currentStateMap.interfaces[0].options, nicholas@2651: canContinue = true; nicholas@3035: nicholas@2825: if (interfaceContext.checkFragmentMinPlays() === false) { nicholas@3035: return; nicholas@3035: } nicholas@3035: if (interfaceContext.checkCommentQuestions() === false) { nicholas@3035: return; nicholas@3035: } nickjillings@1341: nicholas@2538: for (var i = 0; i < checks.length; i++) { nicholas@2538: if (checks[i].type == 'check') { nicholas@2691: var checkState; nicholas@2538: switch (checks[i].name) { nicholas@2538: case 'fragmentPlayed': nicholas@2538: // Check if all fragments have been played n@2790: checkState = interfaceContext.checkAllPlayed(checks[i].errorMessage); nicholas@2691: if (checkState === false) { nicholas@2538: canContinue = false; nicholas@2538: } nicholas@2538: break; nicholas@2538: case 'fragmentFullPlayback': nicholas@2538: // Check all fragments have been played to their full length n@2790: checkState = interfaceContext.checkFragmentsFullyPlayed(checks[i].errorMessage); nicholas@2691: if (checkState === false) { nicholas@2538: canContinue = false; nicholas@2538: } nicholas@2538: break; nicholas@2538: case 'fragmentMoved': nicholas@2538: // Check all fragment sliders have been moved. n@2790: checkState = interfaceContext.checkAllMoved(checks[i].errorMessage); nicholas@2691: if (checkState === false) { nicholas@2538: canContinue = false; nicholas@2538: } nicholas@2538: break; nicholas@2538: case 'fragmentComments': nicholas@2538: // Check all fragment sliders have been moved. n@2790: checkState = interfaceContext.checkAllCommented(checks[i].errorMessage); nicholas@2691: if (checkState === false) { nicholas@2538: canContinue = false; nicholas@2538: } nicholas@2538: break; nicholas@2538: case 'scalerange': nicholas@2538: // Check the scale has been used effectively n@2790: checkState = interfaceContext.checkScaleRange(checks[i].errorMessage); nicholas@2737: if (checkState === false) { nicholas@2737: canContinue = false; nicholas@2737: } nicholas@2538: break; nicholas@2538: default: nicholas@2538: console.log("WARNING - Check option " + checks[i].check + " is not supported on this interface"); nicholas@2538: break; nicholas@2538: } nicholas@2538: nicholas@2538: } nicholas@2538: if (!canContinue) { nicholas@2538: break; nicholas@2538: } nicholas@2538: } nicholas@2538: if (canContinue) { nicholas@2538: if (audioEngineContext.status == 1) { nicholas@2538: var playback = document.getElementById('playback-button'); nicholas@2538: playback.click(); nicholas@2538: // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options nicholas@2538: } else { nicholas@2691: if (audioEngineContext.timer.testStarted === false) { nicholas@2538: interfaceContext.lightbox.post("Warning", 'You have not started the test! Please click play on a sample to begin the test!'); nicholas@2538: return; nicholas@2538: } nicholas@2538: } nicholas@2538: testState.advanceState(); nicholas@2538: } nickjillings@1341: } nickjillings@1341: nicholas@2538: function pageXMLSave(store, pageSpecification) { nicholas@2538: // MANDATORY nicholas@2538: // Saves a specific test page nicholas@2538: // You can use this space to add any extra nodes to your XML saves nicholas@2538: // Get the current information in store (remember to appendChild your data to it) nicholas@2538: // pageSpecification is the current page node configuration nicholas@2538: // To create new XML nodes, use storage.document.createElement(); nicholas@2538: }