babe-project
Advanced tools
Comparing version 0.0.21 to 0.0.22
@@ -1,1 +0,1 @@ | ||
const errors={contactEmail:`There is no contact_email given. Please give a contact_email to the babeInit function,\n\nfor example:\n\nbabeInit({\n ...\n deploy: {\n ...\n contact_email: 'yourcontactemail@email.sample',\n ...\n },\n ...\n});`,prolificURL:`There is no prolificURL given. Please give a prolificURL to the babeInit function,\n\nfor example:\n\nbabeInit({\n ...\n deploy: {\n ...\n prolificURL: 'https://app.prolific.ac/submissions/complete?cc=SAMPLE',\n ...\n },\n ...\n});`,noTrials:`No trials given. Each _babe view takes an object with an obligatory 'trial' property.\n\nfor example:\n\nconst introView = intro({\n ...\n trials: 1,\n ...\n});\n\nYou can find more information at https://github.com/babe-project/babe-base#views-in-_babe`,noName:`No name given. Each _babe view takes an object with an obligatory 'name' property\n\nfor example:\n\nconst introView = intro({\n ...\n name: 'introView',\n ...\n});\n\nYou can find more information at https://github.com/babe-project/babe-base#views-in-_babe`,noData:`No data given. Each _babe view takes an object with an obligatory 'data' property\n\nfor example:\n\nconst mainTrials = forcedChoice({\n ...\n data: my_main_trials,\n ...\n});\n\nThe data is a list of objects defined in your local js file.\n\n_babe's trial views expect each trial object to have specific properties. Here is an example of a forcedCoice view trial:\n\n{\n question: 'How are you today?',\n option1: 'fine',\n option2: 'good'\n}\n\nYou can find more information at https://github.com/babe-project/babe-base#views-in-_babe`,noTrialType:`No trial_type given. Each _babe view takes an object with an obligatory 'trial_type' property\n\nfor example:\n\nconst mainTrials = forcedChoice({\n ...\n trial_type: 'main trials',\n ...\n});\n\nThe trial type is needed for recording the results of your experiment.\n\nYou can find more information at https://github.com/babe-project/babe-base#views-in-_babe`,notAnArray:`The data is not an array. Trial views get an array of objects.\n\nfor example:\n\nconst mainTrials = forcedChoice({\n ...\n data: [\n {\n prop: val,\n prop: val\n },\n {\n prop: val,\n prop:val\n }\n ],\n ...\n});`,noSuchViewName:`The view name listed in progress_bar.in does not exist. Use the view names to reference the views in progress_bar.in.\n\nfor example:\n\nconst mainView = forcedChoice({\n ...\n name: 'myMainView',\n ...\n});\n\nconst introView = intro({\n ...\n name: 'intro',\n ...\n});\n\nbabeInit({\n ...\n progress_bar: {\n in: [\n "myMainView"\n ],\n style: "chunks"\n width: 100\n },\n ...\n});\n`,canvasSort:`No such 'canvas.sort' value. canvas.sort can be 'grid', 'split_grid' or 'random'.\n\nfor example:\n\nconst myTrials = [\n {\n question: 'Are there circles on the picture',\n option1: 'yes',\n option2: 'yes',\n canvas: {\n ...\n sort: 'split_grid'\n ...\n }\n }\n];`};const info={canvasTooSmall:`The canvas size was increased because the default canvas size was too small to fit all the elements.\nBtw, you can manually change the canvas size by passing 'canvasSettings' to the canvas object,\nhowever, your canvas settings might be overridden if needed. \n\nFor example:\n\nconst myTrials = [\n ...\n {\n question: 'Are there circles on the picture',\n option1: 'yes',\n option2: 'no',\n canvas: {\n canvasSettings: {\n height: int,\n width: int\n },\n ...\n }\n },\n ...\n];\n\nSee https://github.com/babe-project/babe-project/blob/master/docs/canvas.md for more information.\n`};const babeInit=function(config){const babe={};babe.views_seq=_.flatten(config.views_seq);babe.currentViewCounter=0;babe.currentTrialCounter=0;babe.currentTrialInViewCounter=0;babe.progress_bar=config.progress_bar;babe.global_data={startDate:Date(),startTime:Date.now()};babe.trial_data=[];babe.deploy=config.deploy;babe.deploy.MTurk_server=babe.deploy.deployMethod=="MTurkSandbox"?"https://workersandbox.mturk.com/mturk/externalSubmit":babe.deploy.deployMethod=="MTurk"?"https://www.mturk.com/mturk/externalSubmit":"";babe.deploy.liveExperiment=babe.deploy.deployMethod!=="debug";babe.deploy.is_MTurk=babe.deploy.MTurk_server!=="";babe.deploy.submissionURL=babe.deploy.deployMethod=="localServer"?"http://localhost:4000/api/submit_experiment/"+babe.deploy.experimentID:babe.deploy.serverAppURL+babe.deploy.experimentID;const regex="/submit_experiment/";babe.deploy.checkExperimentURL=babe.deploy.submissionURL.replace(regex,"/check_experiment/");babe.progress=babeProgress(babe);babe.submission=babeSubmit(babe);babe.findNextView=function(){let currentView=babe.views_seq[babe.currentViewCounter];if(babe.currentTrialInViewCounter<currentView.trials){currentView.render(currentView.CT,babe)}else{babe.currentViewCounter++;currentView=babe.views_seq[babe.currentViewCounter];babe.currentTrialInViewCounter=0;if(currentView!==undefined){currentView.render(currentView.CT,babe)}else{$("#main").html(`<div class='babe-view'>\n <h1 class="title">Nothing more to show</h1>\n </div>`);return}}babe.currentTrialInViewCounter++;babe.currentTrialCounter++;currentView.CT++;if(currentView.hasProgressBar){babe.progress.update()}};(function(){if(babe.deploy.deployMethod==="MTurk"||babe.deploy.deployMethod==="MTurkSandbox"){console.info(`The experiment runs on MTurk (or MTurk's sandbox)\n----------------------------\n\nThe ID of your experiment is ${babe.deploy.experimentID}\n\nThe results will be submitted ${babe.deploy.submissionURL}\n\nand\n\nMTurk's server: ${babe.deploy.MTurk_server}`)}else if(babe.deploy.deployMethod==="Prolific"){console.info(`The experiment runs on Prolific\n-------------------------------\n\nThe ID of your experiment is ${babe.deploy.experimentID}\n\nThe results will be submitted to ${babe.deploy.submissionURL}\n\nwith\n\nProlific URL (must be the same as in the website): ${babe.deploy.prolificURL}`)}else if(babe.deploy.deployMethod==="directLink"){console.info(`The experiment uses Direct Link\n-------------------------------\n\nThe ID of your experiment is ${babe.deploy.experimentID}\n\nThe results will be submitted to ${babe.deploy.submissionURL}`)}else if(babe.deploy.deployMethod==="debug"){console.info(`The experiment is in Debug Mode\n-------------------------------\n\nThe results will be displayed in a table at the end of the experiment and available to download in CSV format.`)}else if(babe.deploy.deployMethod!=="localServer"){throw new Error(`There is no such deployMethod.\n\nPlease use 'debug', 'directLink', 'Mturk', 'MTurkSandbox', 'localServer' or 'Prolific'.\n\nThe deploy method you provided is '${babe.deploy.deployMethod}'.\n\nYou can find more information at https://github.com/babe-project/babe-base`)}if(babe.deploy.deployMethod==="Prolific"&&(babe.deploy.prolificURL===undefined||babe.deploy.prolificURL==="")){throw new Error(errors.prolificURL)}if(babe.deploy.contact_email===undefined||babe.deploy.contact_email===""){throw new Error(errors.contactEmail)}})();if(babe.deploy.deployMethod!=="debug"){$.ajax({type:"GET",url:babe.deploy.checkExperimentURL,crossDomain:true,success:function(responseData,textStatus,jqXHR){babe.progress.add();babe.findNextView()},error:function(jqXHR,textStatus,error){console.log(babe.deploy.checkExperimentURL);alert(`Sorry, there is an error communicating with our server and the experiment cannot proceed. Please return the HIT immediately and contact the author at ${babe.deploy.contact_email}. Please include the following error message: "${jqXHR.responseText}". Thank you for your understanding.`)}})}else{babe.progress.add();babe.findNextView()}};const babeProgress=function(babe){let totalProgressParts=0;let progressTrials=0;let totalProgressChunks=0;let filledChunks=0;let fillChunk=false;const progress={add:function(){babe.views_seq.map(view=>{for(let j=0;j<babe.progress_bar.in.length;j++){if(view.name===babe.progress_bar.in[j]){totalProgressChunks++;totalProgressParts+=view.trials;view.hasProgressBar=true}}})},update:function(){try{addToDOM()}catch(e){console.error(e.message)}const progressBars=$(".progress-bar");let div,filledPart;if(babe.progress_bar.style==="default"){div=$(".progress-bar").width()/totalProgressParts;filledPart=progressTrials*div}else{div=$(".progress-bar").width()/babe.views_seq[babe.currentViewCounter].trials;filledPart=((babe.currentTrialInViewCounter-1)*div).toFixed(4)}const filledElem=jQuery("<span/>",{id:"filled"}).appendTo(progressBars[filledChunks]);$("#filled").css("width",filledPart);progressTrials++;if(babe.progress_bar.style==="chunks"){if(fillChunk===true){filledChunks++;fillChunk=false}if(filledElem.width().toFixed(4)===($(".progress-bar").width()-div).toFixed(4)){fillChunk=true}for(var i=0;i<filledChunks;i++){progressBars[i].style.backgroundColor="#5187BA"}}}};const addToDOM=function(){var bar;var i;var view=$(".babe-view");var barWidth=babe.progress_bar.width;var clearfix=jQuery("<div/>",{class:"clearfix"});var container=jQuery("<div/>",{class:"progress-bar-container"});view.css("padding-top",30);view.prepend(clearfix);view.prepend(container);if(babe.progress_bar.style==="chunks"){for(i=0;i<totalProgressChunks;i++){bar=jQuery("<div/>",{class:"progress-bar"});bar.css("width",barWidth);container.append(bar)}}else if(babe.progress_bar.style==="separate"){bar=jQuery("<div/>",{class:"progress-bar"});bar.css("width",barWidth);container.append(bar)}else if(babe.progress_bar.style==="default"){bar=jQuery("<div/>",{class:"progress-bar"});bar.css("width",barWidth);container.append(bar)}else{throw new Error('Progress_bar.style can be set to "default", "separate" or "chunks" in experiment.js')}};return progress};function babeSubmit(babe){const submit={submit:function(babe){let data={experiment_id:babe.deploy.experimentID,trials:addEmptyColumns(babe.trial_data)};data=_.merge(babe.global_data,data);if(babe.deploy.is_MTurk){const HITData=getHITData();data["assignment_id"]=HITData["assignmentId"];data["worker_id"]=HITData["workerId"];data["hit_id"]=HITData["hitId"];var form=jQuery("<form/>",{id:"mturk-submission-form",action:babe.deploy.MTurk_server,method:"POST"}).appendTo(".babe-thanks-view");jQuery("<input/>",{type:"hidden",name:"trials",value:JSON.stringify(data)}).appendTo(form);jQuery("<input/>",{type:"hidden",name:"assignmentId",value:HITData["assignmentId"]}).appendTo(form)}if(babe.deploy.liveExperiment){console.log("submits");submitResults(babe.deploy.contact_email,babe.deploy.submissionURL,flattenData(data),babe.deploy)}else{const flattenedData=flattenData(data);jQuery("<div/>",{class:"babe-debug-results",html:formatDebugData(flattenedData)}).appendTo($("#babe-debug-table-container"));createCSVForDownload(flattenedData)}}};function submitResults(contactEmail,submissionURL,data,config){contactEmail=typeof contactEmail!=="undefined"?contactEmail:"not provided";$.ajax({type:"POST",url:submissionURL,crossDomain:true,contentType:"application/json",data:JSON.stringify(data),success:function(responseData,textStatus,jqXHR){console.log(textStatus);$("#warning-message").addClass("babe-nodisplay");$("#thanks-message").removeClass("babe-nodisplay");$("#extra-message").removeClass("babe-nodisplay");if(config.is_MTurk){setTimeout(function(){submitToMTurk(data),500})}},error:function(responseData,textStatus,errorThrown){if(config.is_MTurk){submitToMTurk(data);$("#thanks-message").removeClass("babe-nodisplay")}else{if(textStatus=="timeout"){alert("Oops, the submission timed out. Please try again. If the problem persists, please contact "+contactEmail+", including your ID")}else{alert("Oops, the submission failed. The server says: "+responseData.responseText+"\nPlease try again. If the problem persists, please contact "+contactEmail+"with this error message, including your ID")}}}})}const submitToMTurk=function(){var form=$("#mturk-submission-form");form.submit()};const addEmptyColumns=function(trialData){var columns=[];for(var i=0;i<trialData.length;i++){for(var prop in trialData[i]){if(trialData[i].hasOwnProperty(prop)&&columns.indexOf(prop)===-1){columns.push(prop)}}}for(var j=0;j<trialData.length;j++){for(var k=0;k<columns.length;k++){if(!trialData[j].hasOwnProperty(columns[k])){trialData[j][columns[k]]="NA"}}}return trialData};const formatDebugData=function(flattenedData){var output="<table id='babe-debug-table'>";var t=flattenedData[0];output+="<thead><tr>";for(var key in t){if(t.hasOwnProperty(key)){output+="<th>"+key+"</th>"}}output+="</tr></thead>";output+="<tbody><tr>";var entry="";for(var i=0;i<flattenedData.length;i++){var currentTrial=flattenedData[i];for(var k in t){if(currentTrial.hasOwnProperty(k)){entry=String(currentTrial[k]);output+="<td>"+entry.replace(/ /g," ")+"</td>"}}output+="</tr>"}output+="</tbody></table>";return output};const createCSVForDownload=function(flattenedData){var csvOutput="";var t=flattenedData[0];for(var key in t){if(t.hasOwnProperty(key)){csvOutput+='"'+String(key)+'",'}}csvOutput+="\n";for(var i=0;i<flattenedData.length;i++){var currentTrial=flattenedData[i];for(var k in t){if(currentTrial.hasOwnProperty(k)){csvOutput+='"'+String(currentTrial[k])+'",'}}csvOutput+="\n"}var blob=new Blob([csvOutput],{type:"text/csv"});if(window.navigator.msSaveOrOpenBlob){window.navigator.msSaveBlob(blob,"results.csv")}else{jQuery("<a/>",{class:"babe-view-button",html:"Download the results as CSV",href:window.URL.createObjectURL(blob),download:"results.csv"}).appendTo($(".babe-thanks-view"))}};const flattenData=function(data){var trials=data.trials;delete data.trials;var sample_trial=trials[0];for(var trial_key in sample_trial){if(sample_trial.hasOwnProperty(trial_key)){if(data.hasOwnProperty(trial_key)){var new_data_key="glb_"+trial_key;data[new_data_key]=data[trial_key];delete data[trial_key]}}}var out=_.map(trials,function(t){return _.merge(t,data)});return out};const getHITData=function(){const url=window.location.href;let qArray=url.split("?");let HITData={};if(qArray[1]===undefined){throw new Error("Cannot get participant' s assignmentId from the URL (happens if the experiment does NOT run on MTurk or MTurkSandbox).")}else{qArray=qArray[1].split("&");for(var i=0;i<qArray.length;i++){HITData[qArray[i].split("=")[0]]=qArray[i].split("=")[1]}}return HITData};return submit}const babeUtils={view:{inspector:{params:function(config,view){if(config.trials===undefined||config.trials===""){throw new Error(errors.noTrials.concat(findFile(view)))}if(config.name===undefined||config.name===""){throw new Error(errors.noName.concat(findFile(view)))}},missingData:function(config,view){if(config.data===undefined||config.data===null){throw new Error(errors.noData.concat(this.findFile(view)))}if(config.data instanceof Array===false){throw new Error(errors.notAnArray.concat(this.findFile(view)))}if(config.trial_type===undefined||config.trial_type===""){throw new Error(errors.noTrialType.concat(this.findFile(view)))}},findFile:function(view){return`The problem is in ${view} view type.`}},setter:{title:function(title,dflt){return title===undefined?dflt:title},prolificConfirmText:function(text,dflt){return text===undefined||text===""?dflt:text},buttonText:function(buttonText){return buttonText===undefined||buttonText===""?"Next":buttonText},question:function(question){if(question===undefined||question===""){console.warn("this trial has no 'question'");return""}else{return question}},QUD:function(qud){if(qud===undefined||qud===""){return""}else{return qud}}},createTrialDOM:function(config,enableResponse){const pause=config.pause;const fix_duration=config.fix_duration;const stim_duration=config.stim_duration;const data=config.data;const view=config.view;const evts=config.evts!==undefined?config.evts:{};const showPause=function(resolve,reject){if(pause!==undefined&&typeof pause==="number"&&isNaN(pause)===false){setTimeout(()=>{resolve()},pause)}else{resolve()}};const showFixPoint=function(resolve,reject){if(fix_duration!==undefined&&typeof fix_duration==="number"&&isNaN(fix_duration)===false){const fixPoint=jQuery("<div/>",{class:"babe-view-fix-point"});$(".babe-view-stimulus-container").prepend(fixPoint);setTimeout(()=>{fixPoint.remove();resolve()},fix_duration)}else{resolve()}};const showStim=function(resolve,reject){$(".babe-view-stimulus").removeClass("babe-nodisplay");if(data.picture!==undefined){$(".babe-view-stimulus").prepend(`<div class='babe-view-picture'>\n <img src=${data.picture}>\n </div>`)}if(data.canvas){babeDrawShapes(data.canvas)}resolve(evts.after_stim_shown)};const hideStim=function(resolve,reject){const spacePressed=function(e,resolve){if(e.which===32){$(".babe-view-stimulus").addClass("babe-invisible");$("body").off("keydown",spacePressed);resolve(evts.after_stim_hidden)}};if(view==="imageSelection"){$(".babe-view-stimulus-container").addClass("babe-nodisplay");resolve(evts.after_stim_hidden)}if(stim_duration!==undefined&&typeof stim_duration==="number"&&isNaN(stim_duration)===false){setTimeout(()=>{$(".babe-view-stimulus").addClass("babe-invisible");resolve(evts.after_stim_hidden)},stim_duration)}else{$("body").on("keydown",e=>{spacePressed(e,resolve)})}};new Promise((resolve,reject)=>{showPause(resolve,reject)}).then(()=>{if(evts.after_pause){evts.after_pause(data)}return new Promise((resolve,reject)=>{showFixPoint(resolve,reject)})}).then(()=>{if(evts.after_fix_point){evts.after_fix_point(data)}return new Promise((resolve,reject)=>{showStim(resolve,reject)})}).then(()=>{if(evts.after_stim_shown){evts.after_stim_shown(data)}return new Promise((resolve,reject)=>{hideStim(resolve,reject)})}).then(()=>{if(evts.after_stim_hidden){evts.after_stim_hidden(data)}enableResponse();if(evts.after_response_enabled){evts.after_response_enabled(data)}})}},views_seq:{loop:function(arr,count,shuffleFlag){return _.flatMapDeep(_.range(count),function(i){return arr})},loopShuffled:function(arr,count){return _.flatMapDeep(_.range(count),function(i){return _.shuffle(arr)})}}};const babeViews={intro:function(config){babeUtils.view.inspector.params(config,"intro");const intro={name:config.name,title:babeUtils.view.setter.title(config.title,"Welcome!"),text:config.text,button:babeUtils.view.setter.buttonText(config.buttonText),render:function(CT,babe){let prolificId;const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <section class="babe-text-container">\n <p class="babe-view-text">${this.text}</p>\n </section>\n <button id="next" class='babe-view-button' class="babe-nodisplay">${this.button}</button>\n </div>`;$("#main").html(viewTemplate);const prolificForm=`<p id="prolific-id-form">\n <label for="prolific-id">Please, enter your Prolific ID</label>\n <input type="text" id="prolific-id" />\n </p>`;const next=$("#next");function showNextBtn(){if(prolificId.val().trim()!==""){next.removeClass("babe-nodisplay")}else{next.addClass("babe-nodisplay")}}if(babe.deploy.deployMethod==="Prolific"){$(".babe-text-container").append(prolificForm);next.addClass("babe-nodisplay");prolificId=$("#prolific-id");prolificId.on("keyup",function(){showNextBtn()});prolificId.on("focus",function(){showNextBtn()})}next.on("click",function(){if(babe.deploy.deployMethod==="Prolific"){babe.global_data.prolific_id=prolificId.val().trim()}console.log(babe.global_data.prolific_id);babe.findNextView()})},CT:0,trials:config.trials};return intro},instructions:function(config){babeUtils.view.inspector.params(config,"instructions");const instructions={name:config.name,title:babeUtils.view.setter.title(config.title,"Instructions"),text:config.text,button:babeUtils.view.setter.buttonText(config.buttonText),render:function(CT,babe){const viewTemplate=`<div class="babe-view">\n <h1 class='babe-view-title'>${this.title}</h1>\n <section class="babe-text-container">\n <p class="babe-view-text">${this.text}</p>\n </section>\n <button id="next" class='babe-view-button'>${this.button}</button>\n </div>`;$("#main").html(viewTemplate);$("#next").on("click",function(){babe.findNextView()})},CT:0,trials:config.trials};return instructions},begin:function(config){babeUtils.view.inspector.params(config,"begin experiment");const begin={name:config.name,title:babeUtils.view.setter.title(config.title,"Begin"),text:config.text,button:babeUtils.view.setter.buttonText(config.buttonText),render:function(CT,babe){const viewTemplate=`<div class="babe-view">\n <h1 class='babe-view-title'>${this.title}</h1>\n <section class='babe-text-container'>\n <p class='babe-view-text'>${this.text}</p>\n </section>\n <button id='next' class='babe-view-button'>${this.button}</button>\n </div>`;$("#main").html(viewTemplate);$("#next").on("click",function(){babe.findNextView()})},CT:0,trials:config.trials};return begin},forcedChoice:function(config){babeUtils.view.inspector.missingData(config,"forced choice");babeUtils.view.inspector.params(config,"forced choice");const forcedChoice={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const question=babeUtils.view.setter.question(config.data[CT].question);const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const option1=config.data[CT].option1;const option2=config.data[CT].option2;const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;$("#main").html(viewTemplate);const answerContainerElem=`<div class='babe-view-answer-container'>\n <p class='babe-view-question'>${question}</p>\n <label for='o1' class='babe-response-buttons'>${option1}</label>\n <input type='radio' name='answer' id='o1' value=${option1} />\n <input type='radio' name='answer' id='o2' value=${option2} />\n <label for='o2' class='babe-response-buttons'>${option2}</label>\n </div>`;const enableResponse=function(){$(".babe-view").append(answerContainerElem);$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,response:$("input[name=answer]:checked").val(),RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"forcedChoice"},enableResponse)},CT:0,trials:config.trials};return forcedChoice},sliderRating:function(config){babeUtils.view.inspector.missingData(config,"slider rating");babeUtils.view.inspector.params(config,"slider rating");const sliderRating={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const question=babeUtils.view.setter.question(config.data[CT].question);const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const option1=config.data[CT].optionLeft;const option2=config.data[CT].optionRight;const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-QUD'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;const answerContainerElem=`<p class='babe-view-question'>${question}</p>\n <div class='babe-view-answer-container'>\n <span class='babe-response-slider-option'>${option1}</span>\n <input type='range' id='response' class='babe-response-slider' min='0' max='100' value='50'/>\n <span class='babe-response-slider-option'>${option2}</span>\n </div>\n <button id="next" class='babe-view-button babe-nodisplay'>Next</button>`;$("#main").html(viewTemplate);const enableResponse=function(){let response;$(".babe-view").append(answerContainerElem);response=$("#response");response.on("change",function(){$("#next").removeClass("babe-nodisplay")});response.on("click",function(){$("#next").removeClass("babe-nodisplay")});$("#next").on("click",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,response:response.val(),RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"sliderRating"},enableResponse)},CT:0,trials:config.trials};return sliderRating},textboxInput:function(config){babeUtils.view.inspector.missingData(config,"textbox input");babeUtils.view.inspector.params(config,"textbox input");const textboxInput={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const question=babeUtils.view.setter.question(config.data[CT].question);const minChars=config.data[CT].min_chars===undefined?10:config.data[CT].min_chars;const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;const answerContainerElem=`<p class='babe-view-question'>${question}</p>\n <div class='babe-view-answer-container'>\n <textarea name='textbox-input' rows=10 cols=50 class='babe-response-text' />\n </div>\n <button id='next' class='babe-view-button babe-nodisplay'>next</button>`;$("#main").html(viewTemplate);const enableResponse=function(){let next;let textInput;$(".babe-view").append(answerContainerElem);next=$("#next");textInput=$("textarea");textInput.on("keyup",function(){if(textInput.val().trim().length>minChars){next.removeClass("babe-nodisplay")}else{next.addClass("babe-nodisplay")}});next.on("click",function(){var RT=Date.now()-startingTime;var trial_data={trial_type:config.trial_type,trial_number:CT+1,response:textInput.val().trim(),RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"textboxInput"},enableResponse)},CT:0,trials:config.trials};return textboxInput},dropdownChoice:function(config){babeUtils.view.inspector.missingData(config,"dropdown choice");babeUtils.view.inspector.params(config,"dropdown choice");const dropdownChoice={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const question_left_part=config.data[CT].question_left_part===undefined?"":config.data[CT].question_left_part;const question_right_part=config.data[CT].question_right_part===undefined?"":config.data[CT].question_right_part;const option1=config.data[CT].option1;const option2=config.data[CT].option2;const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;const answerContainerElem=`<div class='babe-view-answer-container babe-response-dropdown'>\n ${question_left_part}\n <select id='response' name='answer'>\n <option disabled selected></option>\n <option value=${option1}>${option1}</option>\n <option value=${option2}>${option2}</option>\n </select>\n ${question_right_part}\n </p>\n <button id='next' class='babe-view-button babe-nodisplay'>Next</button>\n </div>`;$("#main").html(viewTemplate);const enableResponse=function(){let response;$(".babe-view").append(answerContainerElem);response=$("#response");response.on("change",function(){$("#next").removeClass("babe-nodisplay")});$("#next").on("click",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,question:question_left_part.concat("...answer here...").concat(question_right_part),response:$(response).val(),RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"dropdownChoice"},enableResponse)},CT:0,trials:config.trials};return dropdownChoice},ratingScale:function(config){babeUtils.view.inspector.missingData(config,"rating scale");babeUtils.view.inspector.params(config,"rating scale");const ratingScale={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const question=babeUtils.view.setter.question(config.data[CT].question);const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const option1=config.data[CT].optionLeft;const option2=config.data[CT].optionRight;const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;const answerContainerElem=`<p class='babe-view-question'>${question}</p>\n <div class='babe-view-answer-container'>\n <strong class='babe-response-rating-option babe-view-text'>${option1}</strong>\n <label for="1" class='babe-response-rating'>1</label>\n <input type="radio" name="answer" id="1" value="1" />\n <label for="2" class='babe-response-rating'>2</label>\n <input type="radio" name="answer" id="2" value="2" />\n <label for="3" class='babe-response-rating'>3</label>\n <input type="radio" name="answer" id="3" value="3" />\n <label for="4" class='babe-response-rating'>4</label>\n <input type="radio" name="answer" id="4" value="4" />\n <label for="5" class='babe-response-rating'>5</label>\n <input type="radio" name="answer" id="5" value="5" />\n <label for="6" class='babe-response-rating'>6</label>\n <input type="radio" name="answer" id="6" value="6" />\n <label for="7" class='babe-response-rating'>7</label>\n <input type="radio" name="answer" id="7" value="7" />\n <strong class='babe-response-rating-option babe-view-text'>${option2}</strong>\n </div>`;$("#main").html(viewTemplate);const enableResponse=function(){$(".babe-view").append(answerContainerElem);$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,response:$("input[name=answer]:checked").val(),RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"ratingScale"},enableResponse)},CT:0,trials:config.trials};return ratingScale},sentenceChoice:function(config){babeUtils.view.inspector.missingData(config,"sentence choice");babeUtils.view.inspector.params(config,"sentence choice");const sentenceChoice={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const question=babeUtils.view.setter.question(config.data[CT].question);const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const option1=config.data[CT].option1;const option2=config.data[CT].option2;const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;const answerContainerElem=`\n <div class='babe-view-answer-container'>\n <p class='babe-view-question'>${question}</p>\n <label for='s1' class='babe-response-sentence'>${option1}</label>\n <input type='radio' name='answer' id='s1' value="${option1}" />\n <label for='s2' class='babe-response-sentence'>${option2}</label>\n <input type='radio' name='answer' id='s2' value="${option2}" />\n </div>`;$("#main").html(viewTemplate);const enableResponse=function(){$(".babe-view").append(answerContainerElem);$("input[name=answer]").on("change",function(e){console.log(e.target.value);var RT=Date.now()-startingTime;var trial_data={trial_type:config.trial_type,trial_number:CT+1,response:e.target.value,RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"sentenceChoice"},enableResponse)},CT:0,trials:config.trials};return sentenceChoice},imageSelection:function(config){babeUtils.view.inspector.missingData(config,"image selection");babeUtils.view.inspector.params(config,"image selection");const imageSelection={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const question=babeUtils.view.setter.question(config.data[CT].question);const picture1=config.data[CT].picture1;const picture2=config.data[CT].picture2;const option1=config.data[CT].option1;const option2=config.data[CT].option2;const viewTemplate=`<div class="babe-view">\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;const answerContainerElem=`<div class='babe-view-answer-container'>\n <p class='babe-view-question'>${question}</p>\n <label for="img1" class='babe-view-picture babe-response-picture'><img src=${picture1}></label>\n <input type="radio" name="answer" id="img1" value="${option1}" />\n <input type="radio" name="answer" id="img2" value="${option2}" />\n <label for="img2" class='babe-view-picture babe-response-picture'><img src=${picture2}></label>\n </div>`;$("#main").html(viewTemplate);const enableResponse=function(){$(".babe-view").append(answerContainerElem);$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,response:$("input[name=answer]:checked").val(),RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"imageSelection"},enableResponse)},CT:0,trials:config.trials};return imageSelection},keyPress:function(config){babeUtils.view.inspector.missingData(config,"key press");babeUtils.view.inspector.params(config,"key press");const keyPress={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const question=babeUtils.view.setter.question(config.data[CT].question);const key1=config.data[CT].key1;const key2=config.data[CT].key2;const value1=config.data[CT][key1];const value2=config.data[CT][key2];const viewTemplate=`<div class="babe-view">\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-response-keypress-header'><strong>${key1}</strong> = ${value1}, <strong>${key2}</strong> = ${value2}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;$("#main").html(viewTemplate);const handleKeyPress=function(e){const keyPressed=String.fromCharCode(e.which).toLowerCase();if(keyPressed===key1||keyPressed===key2){let correctness;const RT=Date.now()-startingTime;if(config.data[CT].expected===config.data[CT][keyPressed.toLowerCase()]){correctness="correct"}else{correctness="incorrect"}const trial_data={trial_type:config.trial_type,trial_number:CT+1,key_pressed:keyPressed,correctness:correctness,RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}trial_data[config.data[CT].key1]=config.data[CT][key1];trial_data[config.data[CT].key2]=config.data[CT][key2];if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);$("body").off("keydown",handleKeyPress);babe.findNextView()}};const enableResponse=function(){$("body").on("keydown",handleKeyPress)};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"keyPress"},enableResponse)},CT:0,trials:config.trials};return keyPress},selfPacedReading:function(config){babeUtils.view.inspector.missingData(config,"self-paced reading");babeUtils.view.inspector.params(config,"self-paced reading");const spr={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const question=babeUtils.view.setter.question(config.data[CT].question);const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const helpText=config.data[CT].help_text!==undefined?config.data[CT].help_text:"Press the SPACE bar to reveal the words";const title=config.data[CT].title!==undefined?config.data[CT].title:"";const picture=config.data[CT].picture;const option1=config.data[CT].option1;const option2=config.data[CT].option2;const sentenceList=config.data[CT].sentence.trim().split(" | ");let spaceCounter=0;let wordList;let readingTimes=[];const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n <p class='babe-help-text babe-nodisplay'>${helpText}</p>\n <p class='babe-spr-sentence'></p>\n <div class='babe-view-answer-container babe-nodisplay'>\n <p class='babe-view-question'>${question}</p>\n <label for='o1' class='babe-response-buttons'>${option1}</label>\n <input type='radio' name='answer' id='o1' value="${option1}" />\n <input type='radio' name='answer' id='o2' value="${option2}" />\n <label for='o2' class='babe-response-buttons'>${option2}</label>\n </div>\n </div>`;$("#main").html(viewTemplate);startingTime=Date.now();const handleKeyPress=function(e){if(e.which===32&&spaceCounter<wordList.length){wordList[spaceCounter].classList.remove("spr-word-hidden");if(spaceCounter===0){$(".babe-help-text").addClass("babe-invisible")}if(spaceCounter>0){wordList[spaceCounter-1].classList.add("spr-word-hidden")}readingTimes.push(Date.now());spaceCounter++}else if(e.which===32&&spaceCounter===wordList.length){wordList[spaceCounter-1].classList.add("spr-word-hidden");$(".babe-view-answer-container").removeClass("babe-nodisplay");readingTimes.push(Date.now());spaceCounter++}else{$("body").off("keydown",handleKeyPress)}};const enableResponse=function(){$(".babe-help-text").removeClass("babe-nodisplay");sentenceList.map(word=>{$(".babe-spr-sentence").append(`<span class='spr-word spr-word-hidden'>${word}</span>`)});wordList=$(".spr-word").toArray();$("body").on("keydown",handleKeyPress)};babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"spr"},enableResponse);$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;let reactionTimes=readingTimes.reduce((result,current,idx)=>{return result.concat(readingTimes[idx+1]-readingTimes[idx])},[]).filter(item=>isNaN(item)===false);const trial_data={trial_type:config.trial_type,trial_number:CT+1,response:$("input[name=answer]:checked").val(),reactionTimes:reactionTimes,time_spent:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})},CT:0,trials:config.trials};return spr},selfPacedReading_ratingScale:function(config){babeUtils.view.inspector.missingData(config,"self-paced reading ratingScale");babeUtils.view.inspector.params(config,"self-paced reading scale ratingScale");const spr={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const question=babeUtils.view.setter.question(config.data[CT].question);const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const title=config.data[CT].title!==undefined?config.data[CT].title:"";const helpText=config.data[CT].help_text!==undefined?config.data[CT].help_text:"Press the SPACE bar to reveal the words";const picture=config.data[CT].picture;const option1=config.data[CT].optionLeft;const option2=config.data[CT].optionRight;const sentenceList=config.data[CT].sentence.trim().split(" | ");let spaceCounter=0;let wordList;let readingTimes=[];const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n <p class='babe-help-text babe-nodisplay'>${helpText}</p>\n <p class='babe-spr-sentence'></p>\n <div class='babe-view-answer-container babe-nodisplay'>\n <p class='babe-view-question'>${question}</p>\n <strong class='babe-response-rating-option babe-view-text'>${option1}</strong>\n <label for="1" class='babe-response-rating'>1</label>\n <input type="radio" name="answer" id="1" value="1" />\n <label for="2" class='babe-response-rating'>2</label>\n <input type="radio" name="answer" id="2" value="2" />\n <label for="3" class='babe-response-rating'>3</label>\n <input type="radio" name="answer" id="3" value="3" />\n <label for="4" class='babe-response-rating'>4</label>\n <input type="radio" name="answer" id="4" value="4" />\n <label for="5" class='babe-response-rating'>5</label>\n <input type="radio" name="answer" id="5" value="5" />\n <label for="6" class='babe-response-rating'>6</label>\n <input type="radio" name="answer" id="6" value="6" />\n <label for="7" class='babe-response-rating'>7</label>\n <input type="radio" name="answer" id="7" value="7" />\n <strong class='babe-response-rating-option babe-view-text'>${option2}</strong>\n </div>\n </div>`;$("#main").html(viewTemplate);startingTime=Date.now();const handleKeyPress=function(e){if(e.which===32&&spaceCounter<wordList.length){wordList[spaceCounter].classList.remove("spr-word-hidden");if(spaceCounter===0){$(".babe-help-text").addClass("babe-invisible")}if(spaceCounter>0){wordList[spaceCounter-1].classList.add("spr-word-hidden")}readingTimes.push(Date.now());spaceCounter++}else if(e.which===32&&spaceCounter===wordList.length){wordList[spaceCounter-1].classList.add("spr-word-hidden");$(".babe-view-answer-container").removeClass("babe-nodisplay");readingTimes.push(Date.now());spaceCounter++}else{$("body").off("keydown",handleKeyPress)}};const enableResponse=function(){$(".babe-help-text").removeClass("babe-nodisplay");sentenceList.map(word=>{$(".babe-spr-sentence").append(`<span class='spr-word spr-word-hidden'>${word}</span>`)});wordList=$(".spr-word").toArray();$("body").on("keydown",handleKeyPress);$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;let reactionTimes=readingTimes.reduce((result,current,idx)=>{return result.concat(readingTimes[idx+1]-readingTimes[idx])},[]).filter(item=>isNaN(item)===false);const trial_data={trial_type:config.trial_type,trial_number:CT+1,response:$("input[name=answer]:checked").val(),reactionTimes:reactionTimes,time_spent:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"spr"},enableResponse)},CT:0,trials:config.trials};return spr},postTest:function(config){babeUtils.view.inspector.params(config,"post test");const postTest={name:config.name,title:babeUtils.view.setter.title(config.title,"Additional Information"),text:config.text,button:babeUtils.view.setter.buttonText(config.buttonText),render:function(CT,babe){const viewTemplate=`<div class='babe-view babe-post-test-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <section class='babe-text-container'>\n <p class='babe-view-text'>${this.text}</p>\n </section>\n <form>\n <p class='babe-view-text'>\n <label for="age">Age:</label>\n <input type="number" name="age" min="18" max="110" id="age" />\n </p>\n <p class='babe-view-text'>\n <label for="gender">Gender:</label>\n <select id="gender" name="gender">\n <option></option>\n <option value="male">male</option>\n <option value="female">female</option>\n <option value="other">other</option>\n </select>\n </p>\n <p class='babe-view-text'>\n <label for="education">Level of Education:</label>\n <select id="education" name="education">\n <option></option>\n <option value="graduated_high_school">Graduated High School</option>\n <option value="graduated_college">Graduated College</option>\n <option value="higher_degree">Higher Degree</option>\n </select>\n </p>\n <p class='babe-view-text'>\n <label for="languages" name="languages">Native Languages: <br /><span>(i.e. the language(s) spoken at home when you were a child)</</span></label>\n <input type="text" id="languages"/>\n </p>\n <p class="babe-view-text">\n <label for="comments">Further comments</label>\n <textarea name="comments" id="comments"\n rows="6" cols="40"></textarea>\n </p>\n <button id="next" class='babe-view-button'>${this.button}</button>\n </form>\n </div>`;$("#main").html(viewTemplate);$("#next").on("click",function(e){e.preventDefault();babe.global_data.age=$("#age").val();babe.global_data.gender=$("#gender").val();babe.global_data.education=$("#education").val();babe.global_data.languages=$("#languages").val();babe.global_data.comments=$("#comments").val().trim();babe.global_data.endTime=Date.now();babe.global_data.timeSpent=(babe.global_data.endTime-babe.global_data.startTime)/6e4;babe.findNextView()})},CT:0,trials:config.trials};return postTest},thanks:function(config){babeUtils.view.inspector.params(config,"thanks");const thanks={name:config.name,title:babeUtils.view.setter.title(config.title,"Thank you for taking part in this experiment!"),prolificConfirmText:babeUtils.view.setter.prolificConfirmText(config.prolificConfirmText,"Please press the button below to confirm that you completed the experiment with Prolific"),render:function(CT,babe){if(babe.deploy.is_MTurk||babe.deploy.deployMethod==="directLink"){$("#main").html(`<div class='babe-view babe-thanks-view'>\n <h2 id='warning-message' class='babe-warning'>Submitting the data\n <p class='babe-view-text'>please do not close the tab</p>\n <div class='babe-loader'></div>\n </h2>\n <h1 id='thanks-message' class='babe-thanks babe-nodisplay'>${this.title}</h1>\n </div>`)}else if(babe.deploy.deployMethod==="Prolific"){$("#main").html(`<div class='babe-view babe-thanks-view'>\n <h2 id='warning-message' class='babe-warning'>Submitting the data\n <p class='babe-view-text'>please do not close the tab</p>\n <div class='babe-loader'></div>\n </h2>\n <h1 id='thanks-message' class='babe-thanks babe-nodisplay'>${this.title}</h1>\n <p id='extra-message' class='babe-view-text babe-nodisplay'>\n ${this.prolificConfirmText}\n <a href="${babe.deploy.prolificURL}" class="babe-view-button prolific-url">Confirm</a>\n </p>\n </div>`)}else if(babe.deploy.deployMethod==="debug"){$("main").html(`<div id='babe-debug-table-container' class='babe-view babe-thanks-view'>\n <h1 class='babe-view-title'>Debug Mode</h1>\n </div>`)}else{console.error("No such babe.deploy.deployMethod")}babe.submission.submit(babe)},CT:0,trials:1};return thanks}};const babeDrawShapes=function(trialInfo){const canvasHeight=trialInfo.canvasSettings===undefined||trialInfo.canvasSettings.height===undefined?300:trialInfo.canvasSettings.height;const canvasWidth=trialInfo.canvasSettings===undefined||trialInfo.canvasSettings.width===undefined?500:trialInfo.canvasSettings.width;const canvasBg=trialInfo.canvasSettings===undefined||trialInfo.canvasSettings.background===undefined?"white":trialInfo.canvasSettings.background;const createCanvas=function(height,width,bg){const canvas={};const canvasElem=document.createElement("canvas");const context=canvasElem.getContext("2d");canvasElem.classList.add("babe-view-canvas");canvasElem.height=height;canvasElem.width=width;canvasElem.style.backgroundColor=bg;$(".babe-view-stimulus").prepend(canvasElem);canvas.draw=function(shape,size,x,y,color){context.beginPath();if(shape==="circle"){context.arc(x,y,size/2,0,2*Math.PI)}else if(shape==="square"){context.rect(x-size/2,y-size/2,size,size)}else if(shape==="triangle"){var delta=size/(Math.sqrt(3)*2);context.moveTo(x-size/2,y+delta);context.lineTo(x+size/2,y+delta);context.lineTo(x,y-2*delta)}if(color==="blue"){context.fillStyle="#2c89df"}else if(color==="green"){context.fillStyle="#22ce59"}else if(color==="red"){context.fillStyle="#ff6347"}else if(color==="yellow"){context.fillStyle="#ecd70b"}else{context.fillStyle=color}context.closePath();context.fill()};canvas.getTwoSidedCoords=function(rows,gap,number,size,direction="row"){var coords=[];var tempCoords=[];var margin=size/2;var columns,xStart,yStart;rows=rows===0||rows===undefined?1:rows;rows=rows>number?number:rows;gap=gap<=size+margin||gap===undefined?margin+size:gap;columns=Math.ceil(number/rows);xStart=(canvasElem.width-(columns*size+(columns-2)*margin))/2+margin/2-gap/2;yStart=(canvasElem.height-(rows*size+(rows-2)*margin))/2+margin;if(xStart<margin){canvasElem.width+=-2*xStart;xStart=margin}if(yStart<margin){canvasElem.height+=-2*yStart;yStart=margin}for(var i=0;i<rows;i++){for(var j=0;j<number;j++){var xPos,yPos;if(Math.floor(j/columns)===i&&j%columns>=Math.ceil(columns/2)){xPos=xStart+j%columns*size+j%columns*margin+gap;yPos=yStart+i*size+i*margin;tempCoords.push({x:xPos,y:yPos})}else if(Math.floor(j/columns)===i){xPos=xStart+j%columns*size+j%columns*margin;yPos=yStart+i*size+i*margin;tempCoords.push({x:xPos,y:yPos})}}}if(direction==="row"){coords=tempCoords}else if(direction==="side_row"){var leftPart=[];var rightPart=[];for(var i=0;i<tempCoords.length;i++){if(i%columns<columns/2){leftPart.push(tempCoords[i])}else{rightPart.push(tempCoords[i])}}coords=leftPart.concat(rightPart)}else if(direction==="column"){var idx;for(var i=0;i<tempCoords.length;i++){idx=i%rows*columns+Math.floor(i/rows);coords.push(tempCoords[idx])}}return coords};canvas.getRandomCoords=function(number,size){let coords=[];const margin=size/2;(function(){let times;const area=canvasElem.height*canvasElem.width;const minArea=size*size*number*4;if(area<minArea){times=Math.ceil(minArea/area);canvasElem.height=canvasElem.height*times;canvasElem.width=canvasElem.width*times;console.info(info.canvasTooSmall)}else{canvasElem.height=canvasElem.height;canvasElem.width=canvasElem.width}})();const generateCoords=function(){const maxWidth=canvasElem.width-size;const maxHeight=canvasElem.height-size;const xPos=Math.floor(Math.random()*(maxWidth-size))+size;const yPos=Math.floor(Math.random()*(maxHeight-size))+size;return{x:xPos,y:yPos}};const adjustCanvas=function(){const area=canvasElem.height*canvasElem.width;console.log("area "+area);const minArea=size*size*number*2.5;console.log("min area "+minArea);if(area<minArea){canvasElem.height=canvasElem.height*minArea/canvasElem.width;canvasElem.width=canvasElem.width*minArea/canvasElem.height}else{canvasElem.height=canvasElem.height;canvasElem.width=canvasElem.width}};const checkCoords=function(xPos,yPos){for(var i=0;i<coords.length;i++){if(xPos+size+margin>coords[i]["x"]&&xPos-size-margin<coords[i]["x"]&&yPos+size+margin>coords[i]["y"]&&yPos-size-margin<coords[i]["y"]){return false}}return true};const findValidCoords=function(){let tempCoords=generateCoords();if(checkCoords(tempCoords.x,tempCoords.y)){coords.push(tempCoords)}else{findValidCoords()}};for(i=0;i<number;i++){findValidCoords()}return coords};canvas.getGridCoords=function(rows,number,size){var coords=[];var margin=size/2;var columns,xStart,yStart;if(rows===0||rows===undefined){rows=1}else if(rows>number){rows=number}columns=Math.ceil(number/rows);xStart=(canvasElem.width-(columns*size+(columns-2)*margin))/2+margin/2;yStart=(canvasElem.height-(rows*size+(rows-2)*margin))/2+margin;if(xStart<margin){canvasElem.width+=-2*xStart;xStart=margin}if(yStart<margin){console.log("true");canvasElem.height+=-2*yStart;yStart=margin}for(var i=0;i<rows;i++){for(var j=0;j<number;j++){if(Math.floor(j/columns)===i){coords.push({x:xStart+j%columns*size+j%columns*margin,y:yStart+i*size+i*margin})}else{continue}}}return coords};return canvas};const canvas=createCanvas(canvasHeight,canvasWidth,canvasBg);const coords=trialInfo.sort=="grid"?canvas.getGridCoords(trialInfo.rows,trialInfo.total,trialInfo.elemSize):trialInfo.sort=="split_grid"?canvas.getTwoSidedCoords(trialInfo.rows,trialInfo.gap,trialInfo.total,trialInfo.elemSize,trialInfo.direction):canvas.getRandomCoords(trialInfo.total,trialInfo.elemSize);if(trialInfo.start_with==="other"){for(let i=0;i<trialInfo.total;i++){if(i<trialInfo.total-trialInfo.focalNumber){canvas.draw(trialInfo.otherShape,trialInfo.elemSize,coords[i].x,coords[i].y,trialInfo.otherColor)}else{canvas.draw(trialInfo.focalShape,trialInfo.elemSize,coords[i].x,coords[i].y,trialInfo.focalColor)}}}else{for(let i=0;i<trialInfo.total;i++){if(i<trialInfo.focalNumber){canvas.draw(trialInfo.focalShape,trialInfo.elemSize,coords[i].x,coords[i].y,trialInfo.focalColor)}else{canvas.draw(trialInfo.otherShape,trialInfo.elemSize,coords[i].x,coords[i].y,trialInfo.otherColor)}}}}; | ||
const errors={contactEmail:`There is no contact_email given. Please give a contact_email to the babeInit function,\n\nfor example:\n\nbabeInit({\n ...\n deploy: {\n ...\n contact_email: 'yourcontactemail@email.sample',\n ...\n },\n ...\n});`,prolificURL:`There is no prolificURL given. Please give a prolificURL to the babeInit function,\n\nfor example:\n\nbabeInit({\n ...\n deploy: {\n ...\n prolificURL: 'https://app.prolific.ac/submissions/complete?cc=SAMPLE',\n ...\n },\n ...\n});`,noTrials:`No trials given. Each _babe view takes an object with an obligatory 'trial' property.\n\nfor example:\n\nconst introView = intro({\n ...\n trials: 1,\n ...\n});\n\nYou can find more information at https://github.com/babe-project/babe-base#views-in-_babe`,noName:`No name given. Each _babe view takes an object with an obligatory 'name' property\n\nfor example:\n\nconst introView = intro({\n ...\n name: 'introView',\n ...\n});\n\nYou can find more information at https://github.com/babe-project/babe-base#views-in-_babe`,noData:`No data given. Each _babe view takes an object with an obligatory 'data' property\n\nfor example:\n\nconst mainTrials = forcedChoice({\n ...\n data: my_main_trials,\n ...\n});\n\nThe data is a list of objects defined in your local js file.\n\n_babe's trial views expect each trial object to have specific properties. Here is an example of a forcedCoice view trial:\n\n{\n question: 'How are you today?',\n option1: 'fine',\n option2: 'good'\n}\n\nYou can find more information at https://github.com/babe-project/babe-base#views-in-_babe`,noTrialType:`No trial_type given. Each _babe view takes an object with an obligatory 'trial_type' property\n\nfor example:\n\nconst mainTrials = forcedChoice({\n ...\n trial_type: 'main trials',\n ...\n});\n\nThe trial type is needed for recording the results of your experiment.\n\nYou can find more information at https://github.com/babe-project/babe-base#views-in-_babe`,notAnArray:`The data is not an array. Trial views get an array of objects.\n\nfor example:\n\nconst mainTrials = forcedChoice({\n ...\n data: [\n {\n prop: val,\n prop: val\n },\n {\n prop: val,\n prop:val\n }\n ],\n ...\n});`,noSuchViewName:`The view name listed in progress_bar.in does not exist. Use the view names to reference the views in progress_bar.in.\n\nfor example:\n\nconst mainView = forcedChoice({\n ...\n name: 'myMainView',\n ...\n});\n\nconst introView = intro({\n ...\n name: 'intro',\n ...\n});\n\nbabeInit({\n ...\n progress_bar: {\n in: [\n "myMainView"\n ],\n style: "chunks"\n width: 100\n },\n ...\n});\n`,canvasSort:`No such 'canvas.sort' value. canvas.sort can be 'grid', 'split_grid' or 'random'.\n\nfor example:\n\nconst myTrials = [\n {\n question: 'Are there circles on the picture',\n option1: 'yes',\n option2: 'yes',\n canvas: {\n ...\n sort: 'split_grid'\n ...\n }\n }\n];`};const info={canvasTooSmall:`The canvas size was increased because the default canvas size was too small to fit all the elements.\nBtw, you can manually change the canvas size by passing 'canvasSettings' to the canvas object,\nhowever, your canvas settings might be overridden if needed. \n\nFor example:\n\nconst myTrials = [\n ...\n {\n question: 'Are there circles on the picture',\n option1: 'yes',\n option2: 'no',\n canvas: {\n canvasSettings: {\n height: int,\n width: int\n },\n ...\n }\n },\n ...\n];\n\nSee https://github.com/babe-project/babe-project/blob/master/docs/canvas.md for more information.\n`};const babeInit=function(config){const babe={};babe.views_seq=_.flatten(config.views_seq);babe.currentViewCounter=0;babe.currentTrialCounter=0;babe.currentTrialInViewCounter=0;babe.progress_bar=config.progress_bar;babe.global_data={startDate:Date(),startTime:Date.now()};babe.trial_data=[];babe.deploy=config.deploy;babe.deploy.MTurk_server=babe.deploy.deployMethod=="MTurkSandbox"?"https://workersandbox.mturk.com/mturk/externalSubmit":babe.deploy.deployMethod=="MTurk"?"https://www.mturk.com/mturk/externalSubmit":"";babe.deploy.liveExperiment=babe.deploy.deployMethod!=="debug";babe.deploy.is_MTurk=babe.deploy.MTurk_server!=="";babe.deploy.submissionURL=babe.deploy.deployMethod=="localServer"?"http://localhost:4000/api/submit_experiment/"+babe.deploy.experimentID:babe.deploy.serverAppURL+babe.deploy.experimentID;const regex="/submit_experiment/";babe.deploy.checkExperimentURL=babe.deploy.submissionURL.replace(regex,"/check_experiment/");babe.progress=babeProgress(babe);babe.submission=babeSubmit(babe);babe.findNextView=function(){let currentView=babe.views_seq[babe.currentViewCounter];if(babe.currentTrialInViewCounter<currentView.trials){currentView.render(currentView.CT,babe)}else{babe.currentViewCounter++;currentView=babe.views_seq[babe.currentViewCounter];babe.currentTrialInViewCounter=0;if(currentView!==undefined){currentView.render(currentView.CT,babe)}else{$("#main").html(`<div class='babe-view'>\n <h1 class="title">Nothing more to show</h1>\n </div>`);return}}babe.currentTrialInViewCounter++;babe.currentTrialCounter++;currentView.CT++;if(currentView.hasProgressBar){babe.progress.update()}};(function(){if(babe.deploy.deployMethod==="MTurk"||babe.deploy.deployMethod==="MTurkSandbox"){console.info(`The experiment runs on MTurk (or MTurk's sandbox)\n----------------------------\n\nThe ID of your experiment is ${babe.deploy.experimentID}\n\nThe results will be submitted ${babe.deploy.submissionURL}\n\nand\n\nMTurk's server: ${babe.deploy.MTurk_server}`)}else if(babe.deploy.deployMethod==="Prolific"){console.info(`The experiment runs on Prolific\n-------------------------------\n\nThe ID of your experiment is ${babe.deploy.experimentID}\n\nThe results will be submitted to ${babe.deploy.submissionURL}\n\nwith\n\nProlific URL (must be the same as in the website): ${babe.deploy.prolificURL}`)}else if(babe.deploy.deployMethod==="directLink"){console.info(`The experiment uses Direct Link\n-------------------------------\n\nThe ID of your experiment is ${babe.deploy.experimentID}\n\nThe results will be submitted to ${babe.deploy.submissionURL}`)}else if(babe.deploy.deployMethod==="debug"){console.info(`The experiment is in Debug Mode\n-------------------------------\n\nThe results will be displayed in a table at the end of the experiment and available to download in CSV format.`)}else if(babe.deploy.deployMethod!=="localServer"){throw new Error(`There is no such deployMethod.\n\nPlease use 'debug', 'directLink', 'Mturk', 'MTurkSandbox', 'localServer' or 'Prolific'.\n\nThe deploy method you provided is '${babe.deploy.deployMethod}'.\n\nYou can find more information at https://github.com/babe-project/babe-base`)}if(babe.deploy.deployMethod==="Prolific"&&(babe.deploy.prolificURL===undefined||babe.deploy.prolificURL==="")){throw new Error(errors.prolificURL)}if(babe.deploy.contact_email===undefined||babe.deploy.contact_email===""){throw new Error(errors.contactEmail)}})();if(babe.deploy.deployMethod!=="debug"){$.ajax({type:"GET",url:babe.deploy.checkExperimentURL,crossDomain:true,success:function(responseData,textStatus,jqXHR){babe.progress.add();babe.findNextView()},error:function(jqXHR,textStatus,error){console.log(babe.deploy.checkExperimentURL);alert(`Sorry, there is an error communicating with our server and the experiment cannot proceed. Please return the HIT immediately and contact the author at ${babe.deploy.contact_email}. Please include the following error message: "${jqXHR.responseText}". Thank you for your understanding.`)}})}else{babe.progress.add();babe.findNextView()}};const babeProgress=function(babe){let totalProgressParts=0;let progressTrials=0;let totalProgressChunks=0;let filledChunks=0;let fillChunk=false;const progress={add:function(){babe.views_seq.map(view=>{for(let j=0;j<babe.progress_bar.in.length;j++){if(view.name===babe.progress_bar.in[j]){totalProgressChunks++;totalProgressParts+=view.trials;view.hasProgressBar=true}}})},update:function(){try{addToDOM()}catch(e){console.error(e.message)}const progressBars=$(".progress-bar");let div,filledPart;if(babe.progress_bar.style==="default"){div=$(".progress-bar").width()/totalProgressParts;filledPart=progressTrials*div}else{div=$(".progress-bar").width()/babe.views_seq[babe.currentViewCounter].trials;filledPart=((babe.currentTrialInViewCounter-1)*div).toFixed(4)}const filledElem=jQuery("<span/>",{id:"filled"}).appendTo(progressBars[filledChunks]);$("#filled").css("width",filledPart);progressTrials++;if(babe.progress_bar.style==="chunks"){if(fillChunk===true){filledChunks++;fillChunk=false}if(filledElem.width().toFixed(4)===($(".progress-bar").width()-div).toFixed(4)){fillChunk=true}for(var i=0;i<filledChunks;i++){progressBars[i].style.backgroundColor="#5187BA"}}}};const addToDOM=function(){var bar;var i;var view=$(".babe-view");var barWidth=babe.progress_bar.width;var clearfix=jQuery("<div/>",{class:"clearfix"});var container=jQuery("<div/>",{class:"progress-bar-container"});view.css("padding-top",30);view.prepend(clearfix);view.prepend(container);if(babe.progress_bar.style==="chunks"){for(i=0;i<totalProgressChunks;i++){bar=jQuery("<div/>",{class:"progress-bar"});bar.css("width",barWidth);container.append(bar)}}else if(babe.progress_bar.style==="separate"){bar=jQuery("<div/>",{class:"progress-bar"});bar.css("width",barWidth);container.append(bar)}else if(babe.progress_bar.style==="default"){bar=jQuery("<div/>",{class:"progress-bar"});bar.css("width",barWidth);container.append(bar)}else{throw new Error('Progress_bar.style can be set to "default", "separate" or "chunks" in experiment.js')}};return progress};function babeSubmit(babe){const submit={submit:function(babe){let data={experiment_id:babe.deploy.experimentID,trials:addEmptyColumns(babe.trial_data)};data=_.merge(babe.global_data,data);if(babe.deploy.is_MTurk){const HITData=getHITData();data["assignment_id"]=HITData["assignmentId"];data["worker_id"]=HITData["workerId"];data["hit_id"]=HITData["hitId"];var form=jQuery("<form/>",{id:"mturk-submission-form",action:babe.deploy.MTurk_server,method:"POST"}).appendTo(".babe-thanks-view");jQuery("<input/>",{type:"hidden",name:"trials",value:JSON.stringify(data)}).appendTo(form);jQuery("<input/>",{type:"hidden",name:"assignmentId",value:HITData["assignmentId"]}).appendTo(form)}if(babe.deploy.liveExperiment){console.log("submits");submitResults(babe.deploy.contact_email,babe.deploy.submissionURL,flattenData(data),babe.deploy)}else{const flattenedData=flattenData(data);jQuery("<div/>",{class:"babe-debug-results",html:formatDebugData(flattenedData)}).appendTo($("#babe-debug-table-container"));createCSVForDownload(flattenedData)}}};function submitResults(contactEmail,submissionURL,data,config){contactEmail=typeof contactEmail!=="undefined"?contactEmail:"not provided";$.ajax({type:"POST",url:submissionURL,crossDomain:true,contentType:"application/json",data:JSON.stringify(data),success:function(responseData,textStatus,jqXHR){console.log(textStatus);$("#warning-message").addClass("babe-nodisplay");$("#thanks-message").removeClass("babe-nodisplay");$("#extra-message").removeClass("babe-nodisplay");if(config.is_MTurk){setTimeout(function(){submitToMTurk(data),500})}},error:function(responseData,textStatus,errorThrown){if(config.is_MTurk){submitToMTurk(data);$("#thanks-message").removeClass("babe-nodisplay")}else{if(textStatus=="timeout"){alert("Oops, the submission timed out. Please try again. If the problem persists, please contact "+contactEmail+", including your ID")}else{alert("Oops, the submission failed. The server says: "+responseData.responseText+"\nPlease try again. If the problem persists, please contact "+contactEmail+"with this error message, including your ID")}}}})}const submitToMTurk=function(){var form=$("#mturk-submission-form");form.submit()};const addEmptyColumns=function(trialData){var columns=[];for(var i=0;i<trialData.length;i++){for(var prop in trialData[i]){if(trialData[i].hasOwnProperty(prop)&&columns.indexOf(prop)===-1){columns.push(prop)}}}for(var j=0;j<trialData.length;j++){for(var k=0;k<columns.length;k++){if(!trialData[j].hasOwnProperty(columns[k])){trialData[j][columns[k]]="NA"}}}return trialData};const formatDebugData=function(flattenedData){var output="<table id='babe-debug-table'>";var t=flattenedData[0];output+="<thead><tr>";for(var key in t){if(t.hasOwnProperty(key)){output+="<th>"+key+"</th>"}}output+="</tr></thead>";output+="<tbody><tr>";var entry="";for(var i=0;i<flattenedData.length;i++){var currentTrial=flattenedData[i];for(var k in t){if(currentTrial.hasOwnProperty(k)){entry=String(currentTrial[k]);output+="<td>"+entry.replace(/ /g," ")+"</td>"}}output+="</tr>"}output+="</tbody></table>";return output};const createCSVForDownload=function(flattenedData){var csvOutput="";var t=flattenedData[0];for(var key in t){if(t.hasOwnProperty(key)){csvOutput+='"'+String(key)+'",'}}csvOutput+="\n";for(var i=0;i<flattenedData.length;i++){var currentTrial=flattenedData[i];for(var k in t){if(currentTrial.hasOwnProperty(k)){csvOutput+='"'+String(currentTrial[k])+'",'}}csvOutput+="\n"}var blob=new Blob([csvOutput],{type:"text/csv"});if(window.navigator.msSaveOrOpenBlob){window.navigator.msSaveBlob(blob,"results.csv")}else{jQuery("<a/>",{class:"babe-view-button",html:"Download the results as CSV",href:window.URL.createObjectURL(blob),download:"results.csv"}).appendTo($(".babe-thanks-view"))}};const flattenData=function(data){var trials=data.trials;delete data.trials;var sample_trial=trials[0];for(var trial_key in sample_trial){if(sample_trial.hasOwnProperty(trial_key)){if(data.hasOwnProperty(trial_key)){var new_data_key="glb_"+trial_key;data[new_data_key]=data[trial_key];delete data[trial_key]}}}var out=_.map(trials,function(t){return _.merge(t,data)});return out};const getHITData=function(){const url=window.location.href;let qArray=url.split("?");let HITData={};if(qArray[1]===undefined){throw new Error("Cannot get participant' s assignmentId from the URL (happens if the experiment does NOT run on MTurk or MTurkSandbox).")}else{qArray=qArray[1].split("&");for(var i=0;i<qArray.length;i++){HITData[qArray[i].split("=")[0]]=qArray[i].split("=")[1]}}return HITData};return submit}const babeUtils={view:{inspector:{params:function(config,view){if(config.trials===undefined||config.trials===""){throw new Error(errors.noTrials.concat(findFile(view)))}if(config.name===undefined||config.name===""){throw new Error(errors.noName.concat(findFile(view)))}},missingData:function(config,view){if(config.data===undefined||config.data===null){throw new Error(errors.noData.concat(this.findFile(view)))}if(config.data instanceof Array===false){throw new Error(errors.notAnArray.concat(this.findFile(view)))}if(config.trial_type===undefined||config.trial_type===""){throw new Error(errors.noTrialType.concat(this.findFile(view)))}},findFile:function(view){return`The problem is in ${view} view type.`}},setter:{prop:function(prop,dflt){return prop===undefined?dflt:prop},title:function(title,dflt){return title===undefined?dflt:title},prolificConfirmText:function(text,dflt){return text===undefined||text===""?dflt:text},buttonText:function(buttonText){return buttonText===undefined||buttonText===""?"Next":buttonText},question:function(question){if(question===undefined||question===""){console.warn("this trial has no 'question'");return""}else{return question}},QUD:function(qud){if(qud===undefined||qud===""){return""}else{return qud}}},createTrialDOM:function(config,enableResponse){const pause=config.pause;const fix_duration=config.fix_duration;const stim_duration=config.stim_duration;const data=config.data;const view=config.view;const evts=config.evts!==undefined?config.evts:{};const showPause=function(resolve,reject){if(pause!==undefined&&typeof pause==="number"&&isNaN(pause)===false){setTimeout(()=>{resolve()},pause)}else{resolve()}};const showFixPoint=function(resolve,reject){if(fix_duration!==undefined&&typeof fix_duration==="number"&&isNaN(fix_duration)===false){const fixPoint=jQuery("<div/>",{class:"babe-view-fix-point"});$(".babe-view-stimulus-container").prepend(fixPoint);setTimeout(()=>{fixPoint.remove();resolve()},fix_duration)}else{resolve()}};const showStim=function(resolve,reject){$(".babe-view-stimulus").removeClass("babe-nodisplay");if(data.picture!==undefined){$(".babe-view-stimulus").prepend(`<div class='babe-view-picture'>\n <img src=${data.picture}>\n </div>`)}if(data.canvas){babeDrawShapes(data.canvas)}resolve(evts.after_stim_shown)};const hideStim=function(resolve,reject){const spacePressed=function(e,resolve){if(e.which===32){$(".babe-view-stimulus").addClass("babe-invisible");$("body").off("keydown",spacePressed);resolve(evts.after_stim_hidden)}};if(view==="imageSelection"){$(".babe-view-stimulus-container").addClass("babe-nodisplay");resolve(evts.after_stim_hidden)}if(stim_duration!==undefined&&typeof stim_duration==="number"&&isNaN(stim_duration)===false){setTimeout(()=>{$(".babe-view-stimulus").addClass("babe-invisible");resolve(evts.after_stim_hidden)},stim_duration)}else{$("body").on("keydown",e=>{spacePressed(e,resolve)})}};new Promise((resolve,reject)=>{showPause(resolve,reject)}).then(()=>{if(evts.after_pause){evts.after_pause(data)}return new Promise((resolve,reject)=>{showFixPoint(resolve,reject)})}).then(()=>{if(evts.after_fix_point){evts.after_fix_point(data)}return new Promise((resolve,reject)=>{showStim(resolve,reject)})}).then(()=>{if(evts.after_stim_shown){evts.after_stim_shown(data)}return new Promise((resolve,reject)=>{hideStim(resolve,reject)})}).then(()=>{if(evts.after_stim_hidden){evts.after_stim_hidden(data)}enableResponse();if(evts.after_response_enabled){evts.after_response_enabled(data)}})}},views_seq:{loop:function(arr,count,shuffleFlag){return _.flatMapDeep(_.range(count),function(i){return arr})},loopShuffled:function(arr,count){return _.flatMapDeep(_.range(count),function(i){return _.shuffle(arr)})}}};const babeViews={intro:function(config){babeUtils.view.inspector.params(config,"intro");const intro={name:config.name,title:babeUtils.view.setter.title(config.title,"Welcome!"),text:config.text,button:babeUtils.view.setter.buttonText(config.buttonText),render:function(CT,babe){let prolificId;const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <section class="babe-text-container">\n <p class="babe-view-text">${this.text}</p>\n </section>\n <button id="next" class='babe-view-button' class="babe-nodisplay">${this.button}</button>\n </div>`;$("#main").html(viewTemplate);const prolificForm=`<p id="prolific-id-form">\n <label for="prolific-id">Please, enter your Prolific ID</label>\n <input type="text" id="prolific-id" />\n </p>`;const next=$("#next");function showNextBtn(){if(prolificId.val().trim()!==""){next.removeClass("babe-nodisplay")}else{next.addClass("babe-nodisplay")}}if(babe.deploy.deployMethod==="Prolific"){$(".babe-text-container").append(prolificForm);next.addClass("babe-nodisplay");prolificId=$("#prolific-id");prolificId.on("keyup",function(){showNextBtn()});prolificId.on("focus",function(){showNextBtn()})}next.on("click",function(){if(babe.deploy.deployMethod==="Prolific"){babe.global_data.prolific_id=prolificId.val().trim()}console.log(babe.global_data.prolific_id);babe.findNextView()})},CT:0,trials:config.trials};return intro},instructions:function(config){babeUtils.view.inspector.params(config,"instructions");const instructions={name:config.name,title:babeUtils.view.setter.title(config.title,"Instructions"),text:config.text,button:babeUtils.view.setter.buttonText(config.buttonText),render:function(CT,babe){const viewTemplate=`<div class="babe-view">\n <h1 class='babe-view-title'>${this.title}</h1>\n <section class="babe-text-container">\n <p class="babe-view-text">${this.text}</p>\n </section>\n <button id="next" class='babe-view-button'>${this.button}</button>\n </div>`;$("#main").html(viewTemplate);$("#next").on("click",function(){babe.findNextView()})},CT:0,trials:config.trials};return instructions},begin:function(config){babeUtils.view.inspector.params(config,"begin experiment");const begin={name:config.name,title:babeUtils.view.setter.title(config.title,"Begin"),text:config.text,button:babeUtils.view.setter.buttonText(config.buttonText),render:function(CT,babe){const viewTemplate=`<div class="babe-view">\n <h1 class='babe-view-title'>${this.title}</h1>\n <section class='babe-text-container'>\n <p class='babe-view-text'>${this.text}</p>\n </section>\n <button id='next' class='babe-view-button'>${this.button}</button>\n </div>`;$("#main").html(viewTemplate);$("#next").on("click",function(){babe.findNextView()})},CT:0,trials:config.trials};return begin},forcedChoice:function(config){babeUtils.view.inspector.missingData(config,"forced choice");babeUtils.view.inspector.params(config,"forced choice");const forcedChoice={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const question=babeUtils.view.setter.question(config.data[CT].question);const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const option1=config.data[CT].option1;const option2=config.data[CT].option2;const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;$("#main").html(viewTemplate);const answerContainerElem=`<div class='babe-view-answer-container'>\n <p class='babe-view-question'>${question}</p>\n <label for='o1' class='babe-response-buttons'>${option1}</label>\n <input type='radio' name='answer' id='o1' value=${option1} />\n <input type='radio' name='answer' id='o2' value=${option2} />\n <label for='o2' class='babe-response-buttons'>${option2}</label>\n </div>`;const enableResponse=function(){$(".babe-view").append(answerContainerElem);$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,response:$("input[name=answer]:checked").val(),RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"forcedChoice"},enableResponse)},CT:0,trials:config.trials};return forcedChoice},sliderRating:function(config){babeUtils.view.inspector.missingData(config,"slider rating");babeUtils.view.inspector.params(config,"slider rating");const sliderRating={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const question=babeUtils.view.setter.question(config.data[CT].question);const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const option1=config.data[CT].optionLeft;const option2=config.data[CT].optionRight;const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-QUD'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;const answerContainerElem=`<p class='babe-view-question'>${question}</p>\n <div class='babe-view-answer-container'>\n <span class='babe-response-slider-option'>${option1}</span>\n <input type='range' id='response' class='babe-response-slider' min='0' max='100' value='50'/>\n <span class='babe-response-slider-option'>${option2}</span>\n </div>\n <button id="next" class='babe-view-button babe-nodisplay'>Next</button>`;$("#main").html(viewTemplate);const enableResponse=function(){let response;$(".babe-view").append(answerContainerElem);response=$("#response");response.on("change",function(){$("#next").removeClass("babe-nodisplay")});response.on("click",function(){$("#next").removeClass("babe-nodisplay")});$("#next").on("click",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,response:response.val(),RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"sliderRating"},enableResponse)},CT:0,trials:config.trials};return sliderRating},textboxInput:function(config){babeUtils.view.inspector.missingData(config,"textbox input");babeUtils.view.inspector.params(config,"textbox input");const textboxInput={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const question=babeUtils.view.setter.question(config.data[CT].question);const minChars=config.data[CT].min_chars===undefined?10:config.data[CT].min_chars;const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;const answerContainerElem=`<p class='babe-view-question'>${question}</p>\n <div class='babe-view-answer-container'>\n <textarea name='textbox-input' rows=10 cols=50 class='babe-response-text' />\n </div>\n <button id='next' class='babe-view-button babe-nodisplay'>next</button>`;$("#main").html(viewTemplate);const enableResponse=function(){let next;let textInput;$(".babe-view").append(answerContainerElem);next=$("#next");textInput=$("textarea");textInput.on("keyup",function(){if(textInput.val().trim().length>minChars){next.removeClass("babe-nodisplay")}else{next.addClass("babe-nodisplay")}});next.on("click",function(){var RT=Date.now()-startingTime;var trial_data={trial_type:config.trial_type,trial_number:CT+1,response:textInput.val().trim(),RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"textboxInput"},enableResponse)},CT:0,trials:config.trials};return textboxInput},dropdownChoice:function(config){babeUtils.view.inspector.missingData(config,"dropdown choice");babeUtils.view.inspector.params(config,"dropdown choice");const dropdownChoice={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const question_left_part=config.data[CT].question_left_part===undefined?"":config.data[CT].question_left_part;const question_right_part=config.data[CT].question_right_part===undefined?"":config.data[CT].question_right_part;const option1=config.data[CT].option1;const option2=config.data[CT].option2;const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;const answerContainerElem=`<div class='babe-view-answer-container babe-response-dropdown'>\n ${question_left_part}\n <select id='response' name='answer'>\n <option disabled selected></option>\n <option value=${option1}>${option1}</option>\n <option value=${option2}>${option2}</option>\n </select>\n ${question_right_part}\n </p>\n <button id='next' class='babe-view-button babe-nodisplay'>Next</button>\n </div>`;$("#main").html(viewTemplate);const enableResponse=function(){let response;$(".babe-view").append(answerContainerElem);response=$("#response");response.on("change",function(){$("#next").removeClass("babe-nodisplay")});$("#next").on("click",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,question:question_left_part.concat("...answer here...").concat(question_right_part),response:$(response).val(),RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"dropdownChoice"},enableResponse)},CT:0,trials:config.trials};return dropdownChoice},ratingScale:function(config){babeUtils.view.inspector.missingData(config,"rating scale");babeUtils.view.inspector.params(config,"rating scale");const ratingScale={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const question=babeUtils.view.setter.question(config.data[CT].question);const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const option1=config.data[CT].optionLeft;const option2=config.data[CT].optionRight;const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;const answerContainerElem=`<p class='babe-view-question'>${question}</p>\n <div class='babe-view-answer-container'>\n <strong class='babe-response-rating-option babe-view-text'>${option1}</strong>\n <label for="1" class='babe-response-rating'>1</label>\n <input type="radio" name="answer" id="1" value="1" />\n <label for="2" class='babe-response-rating'>2</label>\n <input type="radio" name="answer" id="2" value="2" />\n <label for="3" class='babe-response-rating'>3</label>\n <input type="radio" name="answer" id="3" value="3" />\n <label for="4" class='babe-response-rating'>4</label>\n <input type="radio" name="answer" id="4" value="4" />\n <label for="5" class='babe-response-rating'>5</label>\n <input type="radio" name="answer" id="5" value="5" />\n <label for="6" class='babe-response-rating'>6</label>\n <input type="radio" name="answer" id="6" value="6" />\n <label for="7" class='babe-response-rating'>7</label>\n <input type="radio" name="answer" id="7" value="7" />\n <strong class='babe-response-rating-option babe-view-text'>${option2}</strong>\n </div>`;$("#main").html(viewTemplate);const enableResponse=function(){$(".babe-view").append(answerContainerElem);$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,response:$("input[name=answer]:checked").val(),RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"ratingScale"},enableResponse)},CT:0,trials:config.trials};return ratingScale},sentenceChoice:function(config){babeUtils.view.inspector.missingData(config,"sentence choice");babeUtils.view.inspector.params(config,"sentence choice");const sentenceChoice={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const question=babeUtils.view.setter.question(config.data[CT].question);const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const option1=config.data[CT].option1;const option2=config.data[CT].option2;const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;const answerContainerElem=`\n <div class='babe-view-answer-container'>\n <p class='babe-view-question'>${question}</p>\n <label for='s1' class='babe-response-sentence'>${option1}</label>\n <input type='radio' name='answer' id='s1' value="${option1}" />\n <label for='s2' class='babe-response-sentence'>${option2}</label>\n <input type='radio' name='answer' id='s2' value="${option2}" />\n </div>`;$("#main").html(viewTemplate);const enableResponse=function(){$(".babe-view").append(answerContainerElem);$("input[name=answer]").on("change",function(e){console.log(e.target.value);var RT=Date.now()-startingTime;var trial_data={trial_type:config.trial_type,trial_number:CT+1,response:e.target.value,RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"sentenceChoice"},enableResponse)},CT:0,trials:config.trials};return sentenceChoice},imageSelection:function(config){babeUtils.view.inspector.missingData(config,"image selection");babeUtils.view.inspector.params(config,"image selection");const imageSelection={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const question=babeUtils.view.setter.question(config.data[CT].question);const picture1=config.data[CT].picture1;const picture2=config.data[CT].picture2;const option1=config.data[CT].option1;const option2=config.data[CT].option2;const viewTemplate=`<div class="babe-view">\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;const answerContainerElem=`<div class='babe-view-answer-container'>\n <p class='babe-view-question'>${question}</p>\n <label for="img1" class='babe-view-picture babe-response-picture'><img src=${picture1}></label>\n <input type="radio" name="answer" id="img1" value="${option1}" />\n <input type="radio" name="answer" id="img2" value="${option2}" />\n <label for="img2" class='babe-view-picture babe-response-picture'><img src=${picture2}></label>\n </div>`;$("#main").html(viewTemplate);const enableResponse=function(){$(".babe-view").append(answerContainerElem);$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,response:$("input[name=answer]:checked").val(),RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"imageSelection"},enableResponse)},CT:0,trials:config.trials};return imageSelection},keyPress:function(config){babeUtils.view.inspector.missingData(config,"key press");babeUtils.view.inspector.params(config,"key press");const keyPress={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const question=babeUtils.view.setter.question(config.data[CT].question);const key1=config.data[CT].key1;const key2=config.data[CT].key2;const value1=config.data[CT][key1];const value2=config.data[CT][key2];const viewTemplate=`<div class="babe-view">\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-response-keypress-header'><strong>${key1}</strong> = ${value1}, <strong>${key2}</strong> = ${value2}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n </div>`;$("#main").html(viewTemplate);const handleKeyPress=function(e){const keyPressed=String.fromCharCode(e.which).toLowerCase();if(keyPressed===key1||keyPressed===key2){let correctness;const RT=Date.now()-startingTime;if(config.data[CT].expected===config.data[CT][keyPressed.toLowerCase()]){correctness="correct"}else{correctness="incorrect"}const trial_data={trial_type:config.trial_type,trial_number:CT+1,key_pressed:keyPressed,correctness:correctness,RT:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}trial_data[config.data[CT].key1]=config.data[CT][key1];trial_data[config.data[CT].key2]=config.data[CT][key2];if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);$("body").off("keydown",handleKeyPress);babe.findNextView()}};const enableResponse=function(){$("body").on("keydown",handleKeyPress)};startingTime=Date.now();babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"keyPress"},enableResponse)},CT:0,trials:config.trials};return keyPress},selfPacedReading:function(config){babeUtils.view.inspector.missingData(config,"self-paced reading");babeUtils.view.inspector.params(config,"self-paced reading");const spr={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const question=babeUtils.view.setter.question(config.data[CT].question);const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const helpText=config.data[CT].help_text!==undefined?config.data[CT].help_text:"Press the SPACE bar to reveal the words";const title=config.data[CT].title!==undefined?config.data[CT].title:"";const picture=config.data[CT].picture;const option1=config.data[CT].option1;const option2=config.data[CT].option2;const sentenceList=config.data[CT].sentence.trim().split(" | ");let spaceCounter=0;let wordList;let readingTimes=[];const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n <p class='babe-help-text babe-nodisplay'>${helpText}</p>\n <p class='babe-spr-sentence'></p>\n <div class='babe-view-answer-container babe-nodisplay'>\n <p class='babe-view-question'>${question}</p>\n <label for='o1' class='babe-response-buttons'>${option1}</label>\n <input type='radio' name='answer' id='o1' value="${option1}" />\n <input type='radio' name='answer' id='o2' value="${option2}" />\n <label for='o2' class='babe-response-buttons'>${option2}</label>\n </div>\n </div>`;$("#main").html(viewTemplate);startingTime=Date.now();const handleKeyPress=function(e){if(e.which===32&&spaceCounter<wordList.length){wordList[spaceCounter].classList.remove("spr-word-hidden");if(spaceCounter===0){$(".babe-help-text").addClass("babe-invisible")}if(spaceCounter>0){wordList[spaceCounter-1].classList.add("spr-word-hidden")}readingTimes.push(Date.now());spaceCounter++}else if(e.which===32&&spaceCounter===wordList.length){wordList[spaceCounter-1].classList.add("spr-word-hidden");$(".babe-view-answer-container").removeClass("babe-nodisplay");readingTimes.push(Date.now());spaceCounter++}else{$("body").off("keydown",handleKeyPress)}};const enableResponse=function(){$(".babe-help-text").removeClass("babe-nodisplay");sentenceList.map(word=>{$(".babe-spr-sentence").append(`<span class='spr-word spr-word-hidden'>${word}</span>`)});wordList=$(".spr-word").toArray();$("body").on("keydown",handleKeyPress)};babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"spr"},enableResponse);$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;let reactionTimes=readingTimes.reduce((result,current,idx)=>{return result.concat(readingTimes[idx+1]-readingTimes[idx])},[]).filter(item=>isNaN(item)===false);const trial_data={trial_type:config.trial_type,trial_number:CT+1,response:$("input[name=answer]:checked").val(),reactionTimes:reactionTimes,time_spent:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})},CT:0,trials:config.trials};return spr},selfPacedReading_ratingScale:function(config){babeUtils.view.inspector.missingData(config,"self-paced reading ratingScale");babeUtils.view.inspector.params(config,"self-paced reading scale ratingScale");const spr={name:config.name,title:babeUtils.view.setter.title(config.title,""),render:function(CT,babe){let startingTime;const question=babeUtils.view.setter.question(config.data[CT].question);const QUD=babeUtils.view.setter.QUD(config.data[CT].QUD);const title=config.data[CT].title!==undefined?config.data[CT].title:"";const helpText=config.data[CT].help_text!==undefined?config.data[CT].help_text:"Press the SPACE bar to reveal the words";const picture=config.data[CT].picture;const option1=config.data[CT].optionLeft;const option2=config.data[CT].optionRight;const sentenceList=config.data[CT].sentence.trim().split(" | ");let spaceCounter=0;let wordList;let readingTimes=[];const viewTemplate=`<div class='babe-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <p class='babe-view-question babe-view-qud'>${QUD}</p>\n <div class='babe-view-stimulus-container'>\n <div class='babe-view-stimulus babe-nodisplay'></div>\n </div>\n <p class='babe-help-text babe-nodisplay'>${helpText}</p>\n <p class='babe-spr-sentence'></p>\n <div class='babe-view-answer-container babe-nodisplay'>\n <p class='babe-view-question'>${question}</p>\n <strong class='babe-response-rating-option babe-view-text'>${option1}</strong>\n <label for="1" class='babe-response-rating'>1</label>\n <input type="radio" name="answer" id="1" value="1" />\n <label for="2" class='babe-response-rating'>2</label>\n <input type="radio" name="answer" id="2" value="2" />\n <label for="3" class='babe-response-rating'>3</label>\n <input type="radio" name="answer" id="3" value="3" />\n <label for="4" class='babe-response-rating'>4</label>\n <input type="radio" name="answer" id="4" value="4" />\n <label for="5" class='babe-response-rating'>5</label>\n <input type="radio" name="answer" id="5" value="5" />\n <label for="6" class='babe-response-rating'>6</label>\n <input type="radio" name="answer" id="6" value="6" />\n <label for="7" class='babe-response-rating'>7</label>\n <input type="radio" name="answer" id="7" value="7" />\n <strong class='babe-response-rating-option babe-view-text'>${option2}</strong>\n </div>\n </div>`;$("#main").html(viewTemplate);startingTime=Date.now();const handleKeyPress=function(e){if(e.which===32&&spaceCounter<wordList.length){wordList[spaceCounter].classList.remove("spr-word-hidden");if(spaceCounter===0){$(".babe-help-text").addClass("babe-invisible")}if(spaceCounter>0){wordList[spaceCounter-1].classList.add("spr-word-hidden")}readingTimes.push(Date.now());spaceCounter++}else if(e.which===32&&spaceCounter===wordList.length){wordList[spaceCounter-1].classList.add("spr-word-hidden");$(".babe-view-answer-container").removeClass("babe-nodisplay");readingTimes.push(Date.now());spaceCounter++}else{$("body").off("keydown",handleKeyPress)}};const enableResponse=function(){$(".babe-help-text").removeClass("babe-nodisplay");sentenceList.map(word=>{$(".babe-spr-sentence").append(`<span class='spr-word spr-word-hidden'>${word}</span>`)});wordList=$(".spr-word").toArray();$("body").on("keydown",handleKeyPress);$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;let reactionTimes=readingTimes.reduce((result,current,idx)=>{return result.concat(readingTimes[idx+1]-readingTimes[idx])},[]).filter(item=>isNaN(item)===false);const trial_data={trial_type:config.trial_type,trial_number:CT+1,response:$("input[name=answer]:checked").val(),reactionTimes:reactionTimes,time_spent:RT};for(let prop in config.data[CT]){if(config.data[CT].hasOwnProperty(prop)){trial_data[prop]=config.data[CT][prop]}}if(config.data[CT].picture!==undefined){trial_data.picture=config.data[CT].picture}if(config.data[CT].canvas!==undefined){for(let prop in config.data[CT].canvas){if(config.data[CT].canvas.hasOwnProperty(prop)){trial_data[prop]=config.data[CT].canvas[prop]}}}babe.trial_data.push(trial_data);babe.findNextView()})};babeUtils.view.createTrialDOM({pause:config.pause,fix_duration:config.fix_duration,stim_duration:config.stim_duration,data:config.data[CT],evts:config.custom_events,view:"spr"},enableResponse)},CT:0,trials:config.trials};return spr},postTest:function(config){babeUtils.view.inspector.params(config,"post test");const postTest={name:config.name,title:babeUtils.view.setter.title(config.title,"Additional Information"),text:config.text,quest:{age:{title:babeUtils.view.setter.prop(config.age_question,"Age")},gender:{title:babeUtils.view.setter.prop(config.gender_question,"Gender"),male:babeUtils.view.setter.prop(config.gender_male,"male"),female:babeUtils.view.setter.prop(config.gender_female,"female"),other:babeUtils.view.setter.prop(config.gender_other,"other")},edu:{title:babeUtils.view.setter.prop(config.edu_question,"Level of Education"),graduated_high_school:babeUtils.view.setter.prop(config.edu_graduated_high_school,"Graduated High School"),graduated_college:babeUtils.view.setter.prop(config.edu_graduated_college,"Graduated College"),higher_degree:babeUtils.view.setter.prop(config.edu_higher_degree,"Higher Degree")},langs:{title:babeUtils.view.setter.prop(config.languages_question,"Native Languages"),text:babeUtils.view.setter.prop(config.languages_more,"(i.e. the language(s) spoken at home when you were a child)")},comments:{title:babeUtils.view.setter.prop(config.comments_question,"Further Comments")}},button:babeUtils.view.setter.buttonText(config.buttonText),render:function(CT,babe){const viewTemplate=`<div class='babe-view babe-post-test-view'>\n <h1 class='babe-view-title'>${this.title}</h1>\n <section class='babe-text-container'>\n <p class='babe-view-text'>${this.text}</p>\n </section>\n <form>\n <p class='babe-view-text'>\n <label for="age">${this.quest.age.title}:</label>\n <input type="number" name="age" min="18" max="110" id="age" />\n </p>\n <p class='babe-view-text'>\n <label for="gender">${this.quest.gender.title}:</label>\n <select id="gender" name="gender">\n <option></option>\n <option value="${this.quest.gender.male}">${this.quest.gender.male}</option>\n <option value="${this.quest.gender.female}">${this.quest.gender.female}</option>\n <option value="${this.quest.gender.other}">${this.quest.gender.other}</option>\n </select>\n </p>\n <p class='babe-view-text'>\n <label for="education">${this.quest.edu.title}:</label>\n <select id="education" name="education">\n <option></option>\n <option value="${this.quest.edu.graduated_high_school}">${this.quest.edu.graduated_high_school}</option>\n <option value="${this.quest.edu.graduated_college}">${this.quest.edu.graduated_college}</option>\n <option value="${this.quest.edu.higher_degree}">${this.quest.edu.higher_degree}</option>\n </select>\n </p>\n <p class='babe-view-text'>\n <label for="languages" name="languages">${this.quest.langs.title}:<br /><span>${this.quest.langs.text}</</span></label>\n <input type="text" id="languages"/>\n </p>\n <p class="babe-view-text">\n <label for="comments">${this.quest.comments.title}</label>\n <textarea name="comments" id="comments"\n rows="6" cols="40"></textarea>\n </p>\n <button id="next" class='babe-view-button'>${this.button}</button>\n </form>\n </div>`;$("#main").html(viewTemplate);$("#next").on("click",function(e){e.preventDefault();babe.global_data.age=$("#age").val();babe.global_data.gender=$("#gender").val();babe.global_data.education=$("#education").val();babe.global_data.languages=$("#languages").val();babe.global_data.comments=$("#comments").val().trim();babe.global_data.endTime=Date.now();babe.global_data.timeSpent=(babe.global_data.endTime-babe.global_data.startTime)/6e4;babe.findNextView()})},CT:0,trials:config.trials};return postTest},thanks:function(config){babeUtils.view.inspector.params(config,"thanks");const thanks={name:config.name,title:babeUtils.view.setter.title(config.title,"Thank you for taking part in this experiment!"),prolificConfirmText:babeUtils.view.setter.prolificConfirmText(config.prolificConfirmText,"Please press the button below to confirm that you completed the experiment with Prolific"),render:function(CT,babe){if(babe.deploy.is_MTurk||babe.deploy.deployMethod==="directLink"){$("#main").html(`<div class='babe-view babe-thanks-view'>\n <h2 id='warning-message' class='babe-warning'>Submitting the data\n <p class='babe-view-text'>please do not close the tab</p>\n <div class='babe-loader'></div>\n </h2>\n <h1 id='thanks-message' class='babe-thanks babe-nodisplay'>${this.title}</h1>\n </div>`)}else if(babe.deploy.deployMethod==="Prolific"){$("#main").html(`<div class='babe-view babe-thanks-view'>\n <h2 id='warning-message' class='babe-warning'>Submitting the data\n <p class='babe-view-text'>please do not close the tab</p>\n <div class='babe-loader'></div>\n </h2>\n <h1 id='thanks-message' class='babe-thanks babe-nodisplay'>${this.title}</h1>\n <p id='extra-message' class='babe-view-text babe-nodisplay'>\n ${this.prolificConfirmText}\n <a href="${babe.deploy.prolificURL}" class="babe-view-button prolific-url">Confirm</a>\n </p>\n </div>`)}else if(babe.deploy.deployMethod==="debug"){$("main").html(`<div id='babe-debug-table-container' class='babe-view babe-thanks-view'>\n <h1 class='babe-view-title'>Debug Mode</h1>\n </div>`)}else{console.error("No such babe.deploy.deployMethod")}babe.submission.submit(babe)},CT:0,trials:1};return thanks}};const babeDrawShapes=function(trialInfo){const canvasHeight=trialInfo.canvasSettings===undefined||trialInfo.canvasSettings.height===undefined?300:trialInfo.canvasSettings.height;const canvasWidth=trialInfo.canvasSettings===undefined||trialInfo.canvasSettings.width===undefined?500:trialInfo.canvasSettings.width;const canvasBg=trialInfo.canvasSettings===undefined||trialInfo.canvasSettings.background===undefined?"white":trialInfo.canvasSettings.background;const createCanvas=function(height,width,bg){const canvas={};const canvasElem=document.createElement("canvas");const context=canvasElem.getContext("2d");canvasElem.classList.add("babe-view-canvas");canvasElem.height=height;canvasElem.width=width;canvasElem.style.backgroundColor=bg;$(".babe-view-stimulus").prepend(canvasElem);canvas.draw=function(shape,size,x,y,color){context.beginPath();if(shape==="circle"){context.arc(x,y,size/2,0,2*Math.PI)}else if(shape==="square"){context.rect(x-size/2,y-size/2,size,size)}else if(shape==="triangle"){var delta=size/(Math.sqrt(3)*2);context.moveTo(x-size/2,y+delta);context.lineTo(x+size/2,y+delta);context.lineTo(x,y-2*delta)}if(color==="blue"){context.fillStyle="#2c89df"}else if(color==="green"){context.fillStyle="#22ce59"}else if(color==="red"){context.fillStyle="#ff6347"}else if(color==="yellow"){context.fillStyle="#ecd70b"}else{context.fillStyle=color}context.closePath();context.fill()};canvas.getTwoSidedCoords=function(rows,gap,number,size,direction="row"){var coords=[];var tempCoords=[];var margin=size/2;var columns,xStart,yStart;rows=rows===0||rows===undefined?1:rows;rows=rows>number?number:rows;gap=gap<=size+margin||gap===undefined?margin+size:gap;columns=Math.ceil(number/rows);xStart=(canvasElem.width-(columns*size+(columns-2)*margin))/2+margin/2-gap/2;yStart=(canvasElem.height-(rows*size+(rows-2)*margin))/2+margin;if(xStart<margin){canvasElem.width+=-2*xStart;xStart=margin}if(yStart<margin){canvasElem.height+=-2*yStart;yStart=margin}for(var i=0;i<rows;i++){for(var j=0;j<number;j++){var xPos,yPos;if(Math.floor(j/columns)===i&&j%columns>=Math.ceil(columns/2)){xPos=xStart+j%columns*size+j%columns*margin+gap;yPos=yStart+i*size+i*margin;tempCoords.push({x:xPos,y:yPos})}else if(Math.floor(j/columns)===i){xPos=xStart+j%columns*size+j%columns*margin;yPos=yStart+i*size+i*margin;tempCoords.push({x:xPos,y:yPos})}}}if(direction==="row"){coords=tempCoords}else if(direction==="side_row"){var leftPart=[];var rightPart=[];for(var i=0;i<tempCoords.length;i++){if(i%columns<columns/2){leftPart.push(tempCoords[i])}else{rightPart.push(tempCoords[i])}}coords=leftPart.concat(rightPart)}else if(direction==="column"){var idx;for(var i=0;i<tempCoords.length;i++){idx=i%rows*columns+Math.floor(i/rows);coords.push(tempCoords[idx])}}return coords};canvas.getRandomCoords=function(number,size){let coords=[];const margin=size/2;(function(){let times;const area=canvasElem.height*canvasElem.width;const minArea=size*size*number*4;if(area<minArea){times=Math.ceil(minArea/area);canvasElem.height=canvasElem.height*times;canvasElem.width=canvasElem.width*times;console.info(info.canvasTooSmall)}else{canvasElem.height=canvasElem.height;canvasElem.width=canvasElem.width}})();const generateCoords=function(){const maxWidth=canvasElem.width-size;const maxHeight=canvasElem.height-size;const xPos=Math.floor(Math.random()*(maxWidth-size))+size;const yPos=Math.floor(Math.random()*(maxHeight-size))+size;return{x:xPos,y:yPos}};const adjustCanvas=function(){const area=canvasElem.height*canvasElem.width;console.log("area "+area);const minArea=size*size*number*2.5;console.log("min area "+minArea);if(area<minArea){canvasElem.height=canvasElem.height*minArea/canvasElem.width;canvasElem.width=canvasElem.width*minArea/canvasElem.height}else{canvasElem.height=canvasElem.height;canvasElem.width=canvasElem.width}};const checkCoords=function(xPos,yPos){for(var i=0;i<coords.length;i++){if(xPos+size+margin>coords[i]["x"]&&xPos-size-margin<coords[i]["x"]&&yPos+size+margin>coords[i]["y"]&&yPos-size-margin<coords[i]["y"]){return false}}return true};const findValidCoords=function(){let tempCoords=generateCoords();if(checkCoords(tempCoords.x,tempCoords.y)){coords.push(tempCoords)}else{findValidCoords()}};for(i=0;i<number;i++){findValidCoords()}return coords};canvas.getGridCoords=function(rows,number,size){var coords=[];var margin=size/2;var columns,xStart,yStart;if(rows===0||rows===undefined){rows=1}else if(rows>number){rows=number}columns=Math.ceil(number/rows);xStart=(canvasElem.width-(columns*size+(columns-2)*margin))/2+margin/2;yStart=(canvasElem.height-(rows*size+(rows-2)*margin))/2+margin;if(xStart<margin){canvasElem.width+=-2*xStart;xStart=margin}if(yStart<margin){console.log("true");canvasElem.height+=-2*yStart;yStart=margin}for(var i=0;i<rows;i++){for(var j=0;j<number;j++){if(Math.floor(j/columns)===i){coords.push({x:xStart+j%columns*size+j%columns*margin,y:yStart+i*size+i*margin})}else{continue}}}return coords};return canvas};const canvas=createCanvas(canvasHeight,canvasWidth,canvasBg);const coords=trialInfo.sort=="grid"?canvas.getGridCoords(trialInfo.rows,trialInfo.total,trialInfo.elemSize):trialInfo.sort=="split_grid"?canvas.getTwoSidedCoords(trialInfo.rows,trialInfo.gap,trialInfo.total,trialInfo.elemSize,trialInfo.direction):canvas.getRandomCoords(trialInfo.total,trialInfo.elemSize);if(trialInfo.start_with==="other"){for(let i=0;i<trialInfo.total;i++){if(i<trialInfo.total-trialInfo.focalNumber){canvas.draw(trialInfo.otherShape,trialInfo.elemSize,coords[i].x,coords[i].y,trialInfo.otherColor)}else{canvas.draw(trialInfo.focalShape,trialInfo.elemSize,coords[i].x,coords[i].y,trialInfo.focalColor)}}}else{for(let i=0;i<trialInfo.total;i++){if(i<trialInfo.focalNumber){canvas.draw(trialInfo.focalShape,trialInfo.elemSize,coords[i].x,coords[i].y,trialInfo.focalColor)}else{canvas.draw(trialInfo.otherShape,trialInfo.elemSize,coords[i].x,coords[i].y,trialInfo.otherColor)}}}}; |
@@ -70,2 +70,3 @@ # \_babe views | ||
### **Optional fields (can be skipped)** | ||
* `title: string` - the title at the top of the view | ||
* `pause: number (in ms)` - blank screen before the fixation point or stimulus show | ||
@@ -126,2 +127,35 @@ * `fix_duration: number (in ms)` - blank screen with fixation point in the middle | ||
* default: *there is no default* | ||
* `age_question: string` | ||
* question about participant's age | ||
* default: 'Age', | ||
* `gender_question: string` | ||
* question about participant's gender | ||
* default: 'Gender' | ||
* `gender_male: string` | ||
* answer option for the gender question | ||
* default: 'male' | ||
* `gender_female: string` | ||
* answer option for the gender question | ||
* default: 'female' | ||
* `gender_other: string` | ||
* answer option for the gender question | ||
* default: 'other' | ||
* `edu_question: string` | ||
* question about participant's level of education | ||
* default: 'Level of Education' | ||
* `edu_graduated_high_school: string` | ||
* answer option for the education question | ||
* default: 'Graduated High School' | ||
* `edu_graduated_college: string` | ||
* answer option for the education question | ||
* default: 'Graduated College' | ||
* `edu_higher_degree: string` | ||
* answer option for the education question | ||
* default: 'Higher Degree' | ||
* `languages_question: string` | ||
* question about participant's native languages | ||
* default: 'Native Languages' | ||
* `languages_more: string` | ||
* more info about what native languages are | ||
* default: '(i.e. the language(s) spoken at home when you were a child)' | ||
@@ -128,0 +162,0 @@ * babeViews.thanks: |
{ | ||
"name": "babe-project", | ||
"version": "0.0.21", | ||
"version": "0.0.22", | ||
"description": "Basic Architecture for Browser-based experiments (https://github.com/babe-project/babe-project)", | ||
@@ -5,0 +5,0 @@ "main": "_babe.min.js", |
@@ -43,2 +43,6 @@ const babeUtils = { | ||
setter: { | ||
prop: function(prop, dflt) { | ||
return prop === undefined ? dflt : prop; | ||
}, | ||
// sets a default title for the views that are not given a title | ||
@@ -45,0 +49,0 @@ title: function(title, dflt) { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
390137
19
3011