Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

babe-project

Package Overview
Dependencies
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

babe-project - npm Package Compare versions

Comparing version 0.0.7 to 0.0.8

_babe.full.min.js

2

_babe.min.js

@@ -1,1 +0,1 @@

function loop(arr,count,shuffleFlag){return _.flatMapDeep(_.range(count),function(i){return arr})}function loopShuffled(arr,count){return _.flatMapDeep(_.range(count),function(i){return _.shuffle(arr)})}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\nvar 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\nvar 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\nvar 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\nvar 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\nvar 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\nvar mainView = forcedChoice({\n ...\n name: 'myMainView',\n ...\n});\n\nvar 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`};const _babeViews={intro:function(config){paramsChecker(config,"intro");const _intro={name:config.name,title:config.title,text:config.text,buttonText:config.buttonText,render:function(CT,_babe){const viewTemplate=`<div class='view'>\n {{# title }}\n <h1 class="title">{{ title }}</h1>\n {{/ title }}\n {{# text }}\n <section class="text-container">\n <p class="text">{{{ text }}}</p>\n </section>\n {{/ text }}\n <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>\n {{# button }}\n <button id="next" class="nodisplay">{{ button }}</button>\n {{/ button }}\n {{^ button }}\n <button id="next" class="nodisplay">Begin Experiment</button>\n {{/ button }}\n </div>`;$("#main").html(Mustache.render(viewTemplate,{title:this.title,text:this.text,button:this.buttonText}));const prolificId=$("#prolific-id");const IDform=$("#prolific-id-form");const next=$("#next");function showNextBtn(){if(prolificId.val().trim()!==""){next.removeClass("nodisplay")}else{next.addClass("nodisplay")}}if(_babe.deploy.deployMethod!=="Prolific"){IDform.addClass("nodisplay");next.removeClass("nodisplay")}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()}_babe.findNextView()})},CT:0,trials:config.trials};return _intro},instructions:function(config){paramsChecker(config,"instructions");const _instructions={name:config.name,title:config.title,text:config.text,buttonText:config.buttonText,render:function(CT,_babe){const viewTemplate=`<div class="view">\n {{# title }}\n <h1>{{ title }}</h1>\n {{/ title }}\n {{# text }}\n <section class="text-container">\n <p class="text">{{ text }}</p>\n </section>\n {{/ text }}\n {{# button }}\n <button id="next">{{ button }}</button>\n {{/ button }}\n </div>`;$("#main").html(Mustache.render(viewTemplate,{title:this.title,text:this.text,button:this.buttonText}));$("#next").on("click",function(){_babe.findNextView()})},CT:0,trials:config.trials};return _instructions},begin:function(config){paramsChecker(config,"begin experiment");const _begin={name:config.name,text:config.text,render:function(CT,_babe){const viewTemplate=`<div class="view">\n {{# text }}\n <section class="text-container">\n <p class="text">{{ text }}</p>\n </section>\n {{/ text }}\n <button id="next">Begin Experiment</button>\n </div>`;$("#main").html(Mustache.render(viewTemplate,{title:this.title,text:this.text}));$("#next").on("click",function(e){_babe.findNextView()})},CT:0,trials:config.trials};return _begin},forcedChoice:function(config){checkTrialView(config,"forced choice");paramsChecker(config,"forced choice");const _forcedChoice={name:config.name,render:function(CT,_babe){const viewTemplate=`<div class="view">\n <div class="picture", align = "center">\n <img src={{picture}} alt="a picture" height="100" width="100">\n </div>\n <p class="question">\n {{# question }}\n {{ question }}\n {{/ question }}\n </p>\n <p class="answer-container buttons-container">\n <label for="yes" class="button-answer">{{ option1 }}</label>\n <input type="radio" name="answer" id="yes" value={{ option1 }} />\n <input type="radio" name="answer" id="no" value={{ option2 }} />\n <label for="no" class="button-answer">{{option2}}</label>\n </p>\n </div>`;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,picture:config.data[CT].picture}));const startingTime=Date.now();$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,option_chosen:$("input[name=answer]:checked").val(),RT:RT};_babe.trial_data.push(trial_data);_babe.findNextView()})},CT:0,trials:config.trials};return _forcedChoice},sliderRating:function(config){checkTrialView(config,"slider rating");paramsChecker(config,"slider rating");const _sliderRating={name:config.name,render:function(CT,_babe){const viewTemplate=`<div class="view">\n <div class="picture", align = "center">\n <img src={{ picture }} alt="a picture" height="100" width="100">\n </div>\n <p class="question">\n {{# question }}\n {{ question }}\n {{/ question }}\n </p>\n <p class="answer-container slider-container">\n <span class="unnatural">{{ option1 }}</span>\n <input type="range" id="response" class="slider-response" min="0" max="100" value="50"/>\n <span class="natural">{{ option2 }}</span>\n </p>\n <button id="next" class="nodisplay">Next</button>\n </div>`;let response;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,picture:config.data[CT].picture}));const startingTime=Date.now();response=$("#response");response.on("change",function(){$("#next").removeClass("nodisplay")});response.on("click",function(){$("#next").removeClass("nodisplay")});$("#next").on("click",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,rating_slider:response.val(),RT:RT};_babe.trial_data.push(trial_data);_babe.findNextView()})},CT:0,trials:config.trials};return _sliderRating},textboxInput:function(config){checkTrialView(config,"textbox input");paramsChecker(config,"textbox input");const _textboxInput={name:config.name,render:function(CT,_babe){const viewTemplate=`<div class="view">\n <p class="question">\n {{# question }}\n {{/ question }}\n {{ question }}\n </p>\n {{# picture }}\n <div class="picture", align = "center">\n <img src={{ picture }} alt="picture" height="100" width="100">\n </div>\n {{/ picture }}\n <p class="answer-container">\n <textarea name="textbox-input" rows=10 cols=50 class="textbox-input" />\n </p>\n <button id="next" class="nodisplay">next</button>\n </div>`;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,picture:config.data[CT].picture}));const next=$("#next");const textInput=$("textarea");const startingTime=Date.now();textInput.on("keyup",function(){if(textInput.val().trim().length>10){next.removeClass("nodisplay")}else{next.addClass("nodisplay")}});next.on("click",function(){var RT=Date.now()-startingTime;var trial_data={trial_type:config.trial_type,trial_number:CT+1,question:config.data[CT].question,text_input:textInput.val().trim(),RT:RT};_babe.trial_data.push(trial_data);_babe.findNextView()})},CT:0,trials:config.trials};return _textboxInput},dropdownChoice:function(config){checkTrialView(config,"dropdown choice");paramsChecker(config,"dropdown choice");const _dropdownChoice={name:config.name,render:function(CT,_babe){const viewTemplate=`<div class="view">\n <div class="picture", align = "center">\n <img src={{ picture }} alt="a picture" height="100" width="100">\n </div>\n\n {{# question }}\n <p class="answer-container dropdown-container">\n <p class="question">\n {{ question }}\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 </p>\n <button id="next" class="nodisplay">Next</button>\n </p>\n {{/ question }}\n {{# questionLeftPart }}\n <p class="answer-container dropdown-container">\n <p class="question">\n {{ questionLeftPart }}\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 {{ questionRightPart }}\n </p>\n <button id="next" class="nodisplay">Next</button>\n </p>\n {{/ questionLeftPart }}\n </div>`;let response;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,questionLeftPart:config.data[CT].questionLeftPart,questionRightPart:config.data[CT].questionRightPart,option1:config.data[CT].option1,option2:config.data[CT].option2,picture:config.data[CT].picture}));const startingTime=Date.now();response=$("#response");response.on("change",function(){$("#next").removeClass("nodisplay")});$("#next").on("click",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,dropdown_choice:$(response).val(),RT:RT};_babe.trial_data.push(trial_data);_babe.findNextView()})},CT:0,trials:config.trials};return _dropdownChoice},ratingScale:function(config){checkTrialView(config,"rating scale");paramsChecker(config,"rating scale");const _ratingScale={name:config.name,render:function(CT,_babe){const viewTemplate=`<div class="view">\n {{# picture }}\n <div class="picture", align = "center">\n <img src={{ picture }} alt="picture" height="100" width="100">\n </div>\n {{/ picture }}\n\n <p class="question">\n {{# question }}\n {{ question }}\n {{/ question }}\n </p>\n\n <p class="answer-container buttons-container">\n <strong>{{ option1 }}</strong>\n <label for="1" class="rating-answer">1</label>\n <input type="radio" name="answer" id="1" value="1" />\n <label for="2" class="rating-answer">2</label>\n <input type="radio" name="answer" id="2" value="2" />\n <label for="3" class="rating-answer">3</label>\n <input type="radio" name="answer" id="3" value="3" />\n <label for="4" class="rating-answer">4</label>\n <input type="radio" name="answer" id="4" value="4" />\n <label for="5" class="rating-answer">5</label>\n <input type="radio" name="answer" id="5" value="5" />\n <label for="6" class="rating-answer">6</label>\n <input type="radio" name="answer" id="6" value="6" />\n <label for="7" class="rating-answer">7</label>\n <input type="radio" name="answer" id="7" value="7" />\n <strong>{{ option2 }}</strong>\n </p>\n </div>`;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,picture:config.data[CT].picture}));const startingTime=Date.now();$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,option_chosen:$("input[name=answer]:checked").val(),RT:RT};_babe.trial_data.push(trial_data);_babe.findNextView()})},CT:0,trials:config.trials};return _ratingScale},sentenceChoice:function(config){checkTrialView(config,"sentence choice");paramsChecker(config,"sentence choice");const _sentenceChoice={name:config.name,render:function(CT,_babe){var viewTemplate=`<div class="view">\n {{# picture }}\n <div class="picture" align = "center">\n <img src={{ picture }} alt="picture" height="100" width="100">\n </div>\n {{/ picture }}\n\n <p class="question">\n {{# question }}\n {{ question }}\n {{/ question }}\n </p>\n\n <p class="answer-container buttons-container">\n <label for="1" class="sentence-selection">{{ option1 }}</label>\n <input type="radio" name="answer" id="1" value="{{ option1 }}"/>\n <label for="2" class="sentence-selection">{{ option2 }}</label>\n <input type="radio" name="answer" id="2" value="{{ option2 }}"/>\n </p>\n </div>`;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,picture:config.data[CT].picture}));var startingTime=Date.now();$("input[name=answer]").on("change",function(){var RT=Date.now()-startingTime;var trial_data={trial_type:config.trial_type,trial_number:CT+1,question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,option_chosen:$("input[name=answer]:checked").val(),RT:RT};_babe.trial_data.push(trial_data);_babe.findNextView()})},CT:0,trials:config.trials};return _sentenceChoice},imageSelection:function(config){checkTrialView(config,"image selection");paramsChecker(config,"image selection");const _imageSelection={name:config.name,render:function(CT,_babe){const viewTemplate=`<div class="view">\n <p class="question">\n {{# question }}\n {{ question }}\n {{/ question }}\n </p>\n\n <p class="answer-container imgs-container">\n <label for="img1" class="img-answer"><img src={{ picture1 }} alt="picture" height="100" width="100"></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="img-answer"><img src={{ picture2 }} alt="picture" height="100" width="100"></label>\n </p>\n </div>`;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,picture1:config.data[CT].picture1,picture2:config.data[CT].picture2}));const startingTime=Date.now();$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,picture1:config.data[CT].picture1,picture2:config.data[CT].picture2,image_selected:$("input[name=answer]:checked").val(),RT:RT};_babe.trial_data.push(trial_data);_babe.findNextView()})},CT:0,trials:config.trials};return _imageSelection},keyPress:function(config){checkTrialView(config,"key press");paramsChecker(config,"key press");const _keyPress={name:config.name,render:function(CT,_babe){const viewTemplate=`<div class="view">\n <h3>{{ key1 }} = {{ value1 }}, {{ key2 }} = {{ value2 }}</h3>\n <p class="question">\n {{# question }}\n {{/ question }}\n {{ question }}\n </p>\n {{# picture }}\n <div class="picture", align = "center">\n <img src={{ picture }} alt="picture" height="100" width="100">\n </div>\n {{/ picture }}\n </div>`;const key1=config.data[CT].key1;const key2=config.data[CT].key2;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,picture:config.data[CT].picture,key1:key1,key2:key2,value1:config.data[CT][key1],value2:config.data[CT][key2]}));const startingTime=Date.now();function handleKeyPress(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,question:config.data[CT].question,expected:config.data[CT].expected,key_pressed:keyPressed,correctness:correctness,RT:RT};trial_data["key1"]=config.data[CT][key1];trial_data["key2"]=config.data[CT][key2];if(config.data[CT].picture!==undefined){trial_data["picture"]=config.data[CT].picture}if(config.data[CT].question!==undefined){trial_data["question"]=config.data[CT].question}_babe.trial_data.push(trial_data);$("body").off("keydown",handleKeyPress);_babe.findNextView()}}$("body").on("keydown",handleKeyPress)},CT:0,trials:config.trials};return _keyPress},postTest:function(config){paramsChecker(config,"post test");const _postTest={name:config.name,title:config.title,text:config.text,buttonText:config.buttonText,render:function(CT,_babe){const viewTemplate=`<div class="view post-test-templ">\n {{# title }}\n <h1>{{ title }}</h1>\n {{/ title }}\n {{# text }}\n <section class="text-container">\n <p class="text">{{ text }}</p>\n </section>\n {{/ text }}\n <form>\n <p>\n <label for="age">Age:</label>\n <input type="number" name="age" min="18" max="110" id="age" />\n </p>\n <p>\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>\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>\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="comment-sect">\n <label for="comments">Further comments</label>\n <textarea name="comments" id="comments"\n rows="6" cols="40"></textarea>\n </p>\n {{# buttonText }}\n <button id="next">{{ buttonText }}</button>\n {{/ buttonText }}\n {{^ buttonText }}\n <button id="next">Next</button>\n {{/ buttonText }}\n </form>\n </div>`;$("#main").html(Mustache.render(viewTemplate,{title:this.title,text:this.text,buttonText:this.buttonText}));$("#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){paramsChecker(config,"thanks");const _thanks={name:config.name,message:config.title,render:function(CT,_babe){var viewTemplate=`<div class="view thanks-templ">\n <h4 class="warning-message">submitting the data\n <div class="loader"></div>\n </h4>\n {{# thanksMessage }}\n <h1 class="thanks-message nodisplay">{{ thanksMessage }}</h1>\n {{/ thanksMessage }}\n {{^ thanksMessage }}\n <h1 class="thanks-message nodisplay">Thank you for taking part in this experiment!</h1>\n {{/ thanksMessage }}\n {{# extraMessage }}\n <h2 class="extra-message nodisplay">{{{ extraMessage }}}</h2>\n {{/ extraMessage }}\n </div>`;if(_babe.deploy.is_MTurk||_babe.deploy.deployMethod==="directLink"){$("#main").html(Mustache.render(viewTemplate,{thanksMessage:this.message}))}else if(_babe.deploy.deployMethod==="Prolific"){$("main").html(Mustache.render(viewTemplate,{thanksMessage:this.message,extraMessage:"Please press the button below to confirm that you completed the experiment with Prolific<br />".concat("<a href=",_babe.deploy.prolificURL,' class="prolific-url">Confirm</a>')}))}else if(_babe.deploy.deployMethod==="debug"){$("main").html(Mustache.render(viewTemplate,{}))}else{console.log("no such _babe.deploy.deployMethod")}_babe.submission.submit(_babe)},CT:0,trials:1};return _thanks}};function paramsChecker(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)))}}function checkTrialView(config,view){if(config.data===undefined||config.data===null){throw new Error(errors.noData.concat(findFile(view)))}if(config.data instanceof Array===false){throw new Error(errors.notAnArray.concat(findFile(view)))}if(config.trial_type===undefined||config.trial_type===""){throw new Error(errors.noTrialType.concat(findFile(view)))}}function findFile(view){return`\n\nThe problem is in ${view} view.`}function _babeInit(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;_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(Mustache.render(`<div class='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{throw new Error(`There is no such deployMethod.\n\nPlease use 'debug', 'directLink', 'Mturk', 'MTurkSandbox' 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)}})();_babe.progress.add();_babe.findNextView()}function _babeProgress(_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}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()===$(".progress-bar").width()-div){fillChunk=true}for(var i=0;i<filledChunks;i++){progressBars[i].style.backgroundColor="#5187BA"}}}};function addToDOM(){var bar;var i;var view=$(".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){try{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:config_deploy.MTurk_server}).appendTo(".thanks-templ");jQuery("<input/>",{type:"hidden",name:"data",value:JSON.stringify(data)}).appendTo(form);jQuery("<input/>",{type:"hidden",name:"assignmentId",value:HITData["assignmentId"]}).appendTo(form)}catch(e){console.error(e)}}if(_babe.deploy.liveExperiment){console.log("submits");submitResults(_babe.deploy.contact_email,_babe.deploy.submissionURL,flattenData(data),_babe.deploy)}else{const flattenedData=flattenData(data);$(".warning-message").addClass("nodisplay");jQuery("<h3/>",{text:"Debug Mode"}).appendTo($(".view"));jQuery("<div/>",{class:"debug-results",html:formatDebugData(flattenedData)}).appendTo($(".view"));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("nodisplay");$(".thanks-message").removeClass("nodisplay");$(".extra-message").removeClass("nodisplay");if(config.is_MTurk){setTimeout(function(){submitToMTurk(data),500})}},error:function(responseData,textStatus,errorThrown){if(config.is_MTurk){submitToMTurk(data);$(".thanks-message").removeClass("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")}}}})}function submitToMTurk(){var form=$("#mturk-submission-form");form.submit()}function addEmptyColumns(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}function formatDebugData(flattenedData){var output="<table id='debugresults'>";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,"&nbsp;")+"</td>"}}output+="</tr>"}output+="</tbody></table>";return output}function createCSVForDownload(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:"button download-btn",html:"Download the results as CSV",href:window.URL.createObjectURL(blob),download:"results.csv"}).appendTo($(".view"))}}function flattenData(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}function getHITData(){const url=window.location.href;const qArray=url.split("?");const 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}
function _babeInit(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;_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(Mustache.render(`<div class='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{throw new Error(`There is no such deployMethod.\n\nPlease use 'debug', 'directLink', 'Mturk', 'MTurkSandbox' 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)}})();_babe.progress.add();_babe.findNextView()}const _babeViews={intro:function(config){paramsChecker(config,"intro");const _intro={name:config.name,title:config.title,text:config.text,buttonText:config.buttonText,render:function(CT,_babe){const viewTemplate=`<div class='view'>\n {{# title }}\n <h1 class="title">{{ title }}</h1>\n {{/ title }}\n {{# text }}\n <section class="text-container">\n <p class="text">{{{ text }}}</p>\n </section>\n {{/ text }}\n <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>\n {{# button }}\n <button id="next" class="nodisplay">{{ button }}</button>\n {{/ button }}\n {{^ button }}\n <button id="next" class="nodisplay">Begin Experiment</button>\n {{/ button }}\n </div>`;$("#main").html(Mustache.render(viewTemplate,{title:this.title,text:this.text,button:this.buttonText}));const prolificId=$("#prolific-id");const IDform=$("#prolific-id-form");const next=$("#next");function showNextBtn(){if(prolificId.val().trim()!==""){next.removeClass("nodisplay")}else{next.addClass("nodisplay")}}if(_babe.deploy.deployMethod!=="Prolific"){IDform.addClass("nodisplay");next.removeClass("nodisplay")}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()}_babe.findNextView()})},CT:0,trials:config.trials};return _intro},instructions:function(config){paramsChecker(config,"instructions");const _instructions={name:config.name,title:config.title,text:config.text,buttonText:config.buttonText,render:function(CT,_babe){const viewTemplate=`<div class="view">\n {{# title }}\n <h1>{{ title }}</h1>\n {{/ title }}\n {{# text }}\n <section class="text-container">\n <p class="text">{{ text }}</p>\n </section>\n {{/ text }}\n {{# button }}\n <button id="next">{{ button }}</button>\n {{/ button }}\n </div>`;$("#main").html(Mustache.render(viewTemplate,{title:this.title,text:this.text,button:this.buttonText}));$("#next").on("click",function(){_babe.findNextView()})},CT:0,trials:config.trials};return _instructions},begin:function(config){paramsChecker(config,"begin experiment");const _begin={name:config.name,text:config.text,render:function(CT,_babe){const viewTemplate=`<div class="view">\n {{# text }}\n <section class="text-container">\n <p class="text">{{ text }}</p>\n </section>\n {{/ text }}\n <button id="next">Begin Experiment</button>\n </div>`;$("#main").html(Mustache.render(viewTemplate,{title:this.title,text:this.text}));$("#next").on("click",function(e){_babe.findNextView()})},CT:0,trials:config.trials};return _begin},forcedChoice:function(config){checkTrialView(config,"forced choice");paramsChecker(config,"forced choice");const _forcedChoice={name:config.name,render:function(CT,_babe){const viewTemplate=`<div class="view">\n <div class="picture", align = "center">\n <img src={{picture}} alt="a picture" height="100" width="100">\n </div>\n <p class="question">\n {{# question }}\n {{ question }}\n {{/ question }}\n </p>\n <p class="answer-container buttons-container">\n <label for="yes" class="button-answer">{{ option1 }}</label>\n <input type="radio" name="answer" id="yes" value={{ option1 }} />\n <input type="radio" name="answer" id="no" value={{ option2 }} />\n <label for="no" class="button-answer">{{option2}}</label>\n </p>\n </div>`;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,picture:config.data[CT].picture}));const startingTime=Date.now();$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,option_chosen:$("input[name=answer]:checked").val(),RT:RT};_babe.trial_data.push(trial_data);_babe.findNextView()})},CT:0,trials:config.trials};return _forcedChoice},sliderRating:function(config){checkTrialView(config,"slider rating");paramsChecker(config,"slider rating");const _sliderRating={name:config.name,render:function(CT,_babe){const viewTemplate=`<div class="view">\n <div class="picture", align = "center">\n <img src={{ picture }} alt="a picture" height="100" width="100">\n </div>\n <p class="question">\n {{# question }}\n {{ question }}\n {{/ question }}\n </p>\n <p class="answer-container slider-container">\n <span class="unnatural">{{ option1 }}</span>\n <input type="range" id="response" class="slider-response" min="0" max="100" value="50"/>\n <span class="natural">{{ option2 }}</span>\n </p>\n <button id="next" class="nodisplay">Next</button>\n </div>`;let response;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,picture:config.data[CT].picture}));const startingTime=Date.now();response=$("#response");response.on("change",function(){$("#next").removeClass("nodisplay")});response.on("click",function(){$("#next").removeClass("nodisplay")});$("#next").on("click",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,rating_slider:response.val(),RT:RT};_babe.trial_data.push(trial_data);_babe.findNextView()})},CT:0,trials:config.trials};return _sliderRating},textboxInput:function(config){checkTrialView(config,"textbox input");paramsChecker(config,"textbox input");const _textboxInput={name:config.name,render:function(CT,_babe){const viewTemplate=`<div class="view">\n <p class="question">\n {{# question }}\n {{/ question }}\n {{ question }}\n </p>\n {{# picture }}\n <div class="picture", align = "center">\n <img src={{ picture }} alt="picture" height="100" width="100">\n </div>\n {{/ picture }}\n <p class="answer-container">\n <textarea name="textbox-input" rows=10 cols=50 class="textbox-input" />\n </p>\n <button id="next" class="nodisplay">next</button>\n </div>`;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,picture:config.data[CT].picture}));const next=$("#next");const textInput=$("textarea");const startingTime=Date.now();textInput.on("keyup",function(){if(textInput.val().trim().length>10){next.removeClass("nodisplay")}else{next.addClass("nodisplay")}});next.on("click",function(){var RT=Date.now()-startingTime;var trial_data={trial_type:config.trial_type,trial_number:CT+1,question:config.data[CT].question,text_input:textInput.val().trim(),RT:RT};_babe.trial_data.push(trial_data);_babe.findNextView()})},CT:0,trials:config.trials};return _textboxInput},dropdownChoice:function(config){checkTrialView(config,"dropdown choice");paramsChecker(config,"dropdown choice");const _dropdownChoice={name:config.name,render:function(CT,_babe){const viewTemplate=`<div class="view">\n <div class="picture", align = "center">\n <img src={{ picture }} alt="a picture" height="100" width="100">\n </div>\n\n {{# question }}\n <p class="answer-container dropdown-container">\n <p class="question">\n {{ question }}\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 </p>\n <button id="next" class="nodisplay">Next</button>\n </p>\n {{/ question }}\n {{# questionLeftPart }}\n <p class="answer-container dropdown-container">\n <p class="question">\n {{ questionLeftPart }}\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 {{ questionRightPart }}\n </p>\n <button id="next" class="nodisplay">Next</button>\n </p>\n {{/ questionLeftPart }}\n </div>`;let response;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,questionLeftPart:config.data[CT].questionLeftPart,questionRightPart:config.data[CT].questionRightPart,option1:config.data[CT].option1,option2:config.data[CT].option2,picture:config.data[CT].picture}));const startingTime=Date.now();response=$("#response");response.on("change",function(){$("#next").removeClass("nodisplay")});$("#next").on("click",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,dropdown_choice:$(response).val(),RT:RT};_babe.trial_data.push(trial_data);_babe.findNextView()})},CT:0,trials:config.trials};return _dropdownChoice},ratingScale:function(config){checkTrialView(config,"rating scale");paramsChecker(config,"rating scale");const _ratingScale={name:config.name,render:function(CT,_babe){const viewTemplate=`<div class="view">\n {{# picture }}\n <div class="picture", align = "center">\n <img src={{ picture }} alt="picture" height="100" width="100">\n </div>\n {{/ picture }}\n\n <p class="question">\n {{# question }}\n {{ question }}\n {{/ question }}\n </p>\n\n <p class="answer-container buttons-container">\n <strong>{{ option1 }}</strong>\n <label for="1" class="rating-answer">1</label>\n <input type="radio" name="answer" id="1" value="1" />\n <label for="2" class="rating-answer">2</label>\n <input type="radio" name="answer" id="2" value="2" />\n <label for="3" class="rating-answer">3</label>\n <input type="radio" name="answer" id="3" value="3" />\n <label for="4" class="rating-answer">4</label>\n <input type="radio" name="answer" id="4" value="4" />\n <label for="5" class="rating-answer">5</label>\n <input type="radio" name="answer" id="5" value="5" />\n <label for="6" class="rating-answer">6</label>\n <input type="radio" name="answer" id="6" value="6" />\n <label for="7" class="rating-answer">7</label>\n <input type="radio" name="answer" id="7" value="7" />\n <strong>{{ option2 }}</strong>\n </p>\n </div>`;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,picture:config.data[CT].picture}));const startingTime=Date.now();$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,option_chosen:$("input[name=answer]:checked").val(),RT:RT};_babe.trial_data.push(trial_data);_babe.findNextView()})},CT:0,trials:config.trials};return _ratingScale},sentenceChoice:function(config){checkTrialView(config,"sentence choice");paramsChecker(config,"sentence choice");const _sentenceChoice={name:config.name,render:function(CT,_babe){var viewTemplate=`<div class="view">\n {{# picture }}\n <div class="picture" align = "center">\n <img src={{ picture }} alt="picture" height="100" width="100">\n </div>\n {{/ picture }}\n\n <p class="question">\n {{# question }}\n {{ question }}\n {{/ question }}\n </p>\n\n <p class="answer-container buttons-container">\n <label for="1" class="sentence-selection">{{ option1 }}</label>\n <input type="radio" name="answer" id="1" value="{{ option1 }}"/>\n <label for="2" class="sentence-selection">{{ option2 }}</label>\n <input type="radio" name="answer" id="2" value="{{ option2 }}"/>\n </p>\n </div>`;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,picture:config.data[CT].picture}));var startingTime=Date.now();$("input[name=answer]").on("change",function(){var RT=Date.now()-startingTime;var trial_data={trial_type:config.trial_type,trial_number:CT+1,question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,option_chosen:$("input[name=answer]:checked").val(),RT:RT};_babe.trial_data.push(trial_data);_babe.findNextView()})},CT:0,trials:config.trials};return _sentenceChoice},imageSelection:function(config){checkTrialView(config,"image selection");paramsChecker(config,"image selection");const _imageSelection={name:config.name,render:function(CT,_babe){const viewTemplate=`<div class="view">\n <p class="question">\n {{# question }}\n {{ question }}\n {{/ question }}\n </p>\n\n <p class="answer-container imgs-container">\n <label for="img1" class="img-answer"><img src={{ picture1 }} alt="picture" height="100" width="100"></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="img-answer"><img src={{ picture2 }} alt="picture" height="100" width="100"></label>\n </p>\n </div>`;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,picture1:config.data[CT].picture1,picture2:config.data[CT].picture2}));const startingTime=Date.now();$("input[name=answer]").on("change",function(){const RT=Date.now()-startingTime;const trial_data={trial_type:config.trial_type,trial_number:CT+1,question:config.data[CT].question,option1:config.data[CT].option1,option2:config.data[CT].option2,picture1:config.data[CT].picture1,picture2:config.data[CT].picture2,image_selected:$("input[name=answer]:checked").val(),RT:RT};_babe.trial_data.push(trial_data);_babe.findNextView()})},CT:0,trials:config.trials};return _imageSelection},keyPress:function(config){checkTrialView(config,"key press");paramsChecker(config,"key press");const _keyPress={name:config.name,render:function(CT,_babe){const viewTemplate=`<div class="view">\n <h3>{{ key1 }} = {{ value1 }}, {{ key2 }} = {{ value2 }}</h3>\n <p class="question">\n {{# question }}\n {{/ question }}\n {{ question }}\n </p>\n {{# picture }}\n <div class="picture", align = "center">\n <img src={{ picture }} alt="picture" height="100" width="100">\n </div>\n {{/ picture }}\n </div>`;const key1=config.data[CT].key1;const key2=config.data[CT].key2;$("#main").html(Mustache.render(viewTemplate,{question:config.data[CT].question,picture:config.data[CT].picture,key1:key1,key2:key2,value1:config.data[CT][key1],value2:config.data[CT][key2]}));const startingTime=Date.now();function handleKeyPress(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,question:config.data[CT].question,expected:config.data[CT].expected,key_pressed:keyPressed,correctness:correctness,RT:RT};trial_data["key1"]=config.data[CT][key1];trial_data["key2"]=config.data[CT][key2];if(config.data[CT].picture!==undefined){trial_data["picture"]=config.data[CT].picture}if(config.data[CT].question!==undefined){trial_data["question"]=config.data[CT].question}_babe.trial_data.push(trial_data);$("body").off("keydown",handleKeyPress);_babe.findNextView()}}$("body").on("keydown",handleKeyPress)},CT:0,trials:config.trials};return _keyPress},postTest:function(config){paramsChecker(config,"post test");const _postTest={name:config.name,title:config.title,text:config.text,buttonText:config.buttonText,render:function(CT,_babe){const viewTemplate=`<div class="view post-test-templ">\n {{# title }}\n <h1>{{ title }}</h1>\n {{/ title }}\n {{# text }}\n <section class="text-container">\n <p class="text">{{ text }}</p>\n </section>\n {{/ text }}\n <form>\n <p>\n <label for="age">Age:</label>\n <input type="number" name="age" min="18" max="110" id="age" />\n </p>\n <p>\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>\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>\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="comment-sect">\n <label for="comments">Further comments</label>\n <textarea name="comments" id="comments"\n rows="6" cols="40"></textarea>\n </p>\n {{# buttonText }}\n <button id="next">{{ buttonText }}</button>\n {{/ buttonText }}\n {{^ buttonText }}\n <button id="next">Next</button>\n {{/ buttonText }}\n </form>\n </div>`;$("#main").html(Mustache.render(viewTemplate,{title:this.title,text:this.text,buttonText:this.buttonText}));$("#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){paramsChecker(config,"thanks");const _thanks={name:config.name,message:config.title,render:function(CT,_babe){var viewTemplate=`<div class="view thanks-templ">\n <h4 class="warning-message">submitting the data\n <div class="loader"></div>\n </h4>\n {{# thanksMessage }}\n <h1 class="thanks-message nodisplay">{{ thanksMessage }}</h1>\n {{/ thanksMessage }}\n {{^ thanksMessage }}\n <h1 class="thanks-message nodisplay">Thank you for taking part in this experiment!</h1>\n {{/ thanksMessage }}\n {{# extraMessage }}\n <h2 class="extra-message nodisplay">{{{ extraMessage }}}</h2>\n {{/ extraMessage }}\n </div>`;if(_babe.deploy.is_MTurk||_babe.deploy.deployMethod==="directLink"){$("#main").html(Mustache.render(viewTemplate,{thanksMessage:this.message}))}else if(_babe.deploy.deployMethod==="Prolific"){$("main").html(Mustache.render(viewTemplate,{thanksMessage:this.message,extraMessage:"Please press the button below to confirm that you completed the experiment with Prolific<br />".concat("<a href=",_babe.deploy.prolificURL,' class="prolific-url">Confirm</a>')}))}else if(_babe.deploy.deployMethod==="debug"){$("main").html(Mustache.render(viewTemplate,{}))}else{console.log("no such _babe.deploy.deployMethod")}_babe.submission.submit(_babe)},CT:0,trials:1};return _thanks}};function paramsChecker(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)))}}function checkTrialView(config,view){if(config.data===undefined||config.data===null){throw new Error(errors.noData.concat(findFile(view)))}if(config.data instanceof Array===false){throw new Error(errors.notAnArray.concat(findFile(view)))}if(config.trial_type===undefined||config.trial_type===""){throw new Error(errors.noTrialType.concat(findFile(view)))}}function findFile(view){return`\n\nThe problem is in ${view} view.`}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\nvar 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\nvar 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\nvar 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\nvar 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\nvar 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\nvar mainView = forcedChoice({\n ...\n name: 'myMainView',\n ...\n});\n\nvar 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`};function _babeProgress(_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}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()===$(".progress-bar").width()-div){fillChunk=true}for(var i=0;i<filledChunks;i++){progressBars[i].style.backgroundColor="#5187BA"}}}};function addToDOM(){var bar;var i;var view=$(".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 loop(arr,count,shuffleFlag){return _.flatMapDeep(_.range(count),function(i){return arr})}function loopShuffled(arr,count){return _.flatMapDeep(_.range(count),function(i){return _.shuffle(arr)})}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){try{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:config_deploy.MTurk_server}).appendTo(".thanks-templ");jQuery("<input/>",{type:"hidden",name:"data",value:JSON.stringify(data)}).appendTo(form);jQuery("<input/>",{type:"hidden",name:"assignmentId",value:HITData["assignmentId"]}).appendTo(form)}catch(e){console.error(e)}}if(_babe.deploy.liveExperiment){console.log("submits");submitResults(_babe.deploy.contact_email,_babe.deploy.submissionURL,flattenData(data),_babe.deploy)}else{const flattenedData=flattenData(data);$(".warning-message").addClass("nodisplay");jQuery("<h3/>",{text:"Debug Mode"}).appendTo($(".view"));jQuery("<div/>",{class:"debug-results",html:formatDebugData(flattenedData)}).appendTo($(".view"));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("nodisplay");$(".thanks-message").removeClass("nodisplay");$(".extra-message").removeClass("nodisplay");if(config.is_MTurk){setTimeout(function(){submitToMTurk(data),500})}},error:function(responseData,textStatus,errorThrown){if(config.is_MTurk){submitToMTurk(data);$(".thanks-message").removeClass("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")}}}})}function submitToMTurk(){var form=$("#mturk-submission-form");form.submit()}function addEmptyColumns(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}function formatDebugData(flattenedData){var output="<table id='debugresults'>";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,"&nbsp;")+"</td>"}}output+="</tr>"}output+="</tbody></table>";return output}function createCSVForDownload(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:"button download-btn",html:"Download the results as CSV",href:window.URL.createObjectURL(blob),download:"results.csv"}).appendTo($(".view"))}}function flattenData(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}function getHITData(){const url=window.location.href;const qArray=url.split("?");const 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}
{
"name": "babe-project",
"version": "0.0.7",
"version": "0.0.8",
"description": "Basic Architecture for Browser-based experiments (https://github.com/babe-project)",

@@ -9,3 +9,4 @@ "main": "babe-main.js",

"jquery": "^3.3.1",
"mustache": "^3.0.0"
"mustache": "^3.0.0",
"uglify-es": "^3.3.9"
},

@@ -12,0 +13,0 @@ "devDependencies": {},

# \_babe project
basic architecture for browser-based experiments
## Installation
#### Install with npm (recommended)
## Creating a \_babe experiment with \_babe
#### Option 1: Download the \_babe project package
1. Download the [\_babe .zip](https://github.com/babe-project/babe-base)
2. Unzip and place the folder (babe-base-master) in the `libraries/` folder of your experiment.
Your experiment's structure should look something like this:
`experiment/`
+ `libraries/`
+ `babe-babe-master`
+ `_babe.full.min.js`
+ `_babe.min.js`
+ ...
+ ...
`_babe.full.min.js` includes the dependencies that \_babe uses (jQuery, Mustache and csv-js).
Using `_babe.full.min.js` there is no need to install and import jQuery, Mustache and csv-js.
`_babe.min.js` includes only the \_babe package, the dependencies should be installed separately for \_babe to work
3. Import \_babe in your `index.html`:
3.1 the full version:
`<script src="libraries/babe-base-master/_babe.full.min.js></script>"`
3.2 no-dependencies version:
`<script src="libraries/babe-base-master/_babe.full.min.js></script>"`
Note: You need to install jQuery, Mustache and csv-js in your experiment.
4. Use \_babe styles:
import \_babe-styles in your `index.html`:
`<link rel="stylesheet" type="text/css" href="libraries/babe-base-master/_babe-styles.css">`
#### Option 2: Install with npm
You need npm installed on your machine. Here is more information on how to [install npm](https://www.npmjs.com/get-npm)

@@ -13,22 +50,18 @@

#### Download the package
Alternatively you can get the package by cloning the repo in your experiment's directory (?)
Import \_babe, jQuery, Mustache and csv-js in your main .html
## Usage
```
<link rel="stylesheet" type="text/css" href="node_modules/babe-project/_babe-styles.css">
### \_babe Initialisation
To initialise the experiment import the babe `babeInit` function in `your_js_file.js`.
<script src="node_modules/mustache/mustache.min.js"></script>
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="node_modules/csv-js/csv.js"></script>
<script src="node_modules/babe-project/_babe.min.js></script>"
```
// your_js_file.js
import { babeInit } from './link_to_your_libraries/babe-project/babe-init.js';
## Usage
...
...
...
```
### \_babe Initialisation
`babeInit` takes an object as a parameter with the following properties:
`_babeInit` takes an object as a parameter with the following properties:

@@ -40,9 +73,7 @@ * `views_seq` - a list of view objects in the sequence you want the to appear in your experiment

Sample `babeInit` call:
Sample `_babeInit` call:
```
import { babeInit } from './link_to_your_libraries/babe-project/babe-init.js';
$("document").ready(function() {
babeInit({
_babeInit({
views_seq: [

@@ -76,3 +107,3 @@ intro,

For \_babe views to render, you need to have an html tag (preferrably `div` or `main`)
\_babe views get inserted in an html element with id `main`, you need to have an html tag (preferrably `div` or `main`)
with `id="main"`

@@ -86,20 +117,5 @@

<head>
<meta charset="UTF-8" />
<title>browser-based experiments</title>
<!-- fonts from Google fonts -->
<link href="https://fonts.googleapis.com/css?family=Noto+Sans:400,700|Noto+Serif:400,700|Playfair+Display:700" rel="stylesheet">
<!-- css -->
<link rel="stylesheet" type="text/css" href="node_modules/babe-project/babe-styles.css">
<!-- js dependencies -->
<script src="node_modules/mustache/mustache.min.js"></script>
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="node_modules/csv-js/csv.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.js"></script>
<!-- load the trials -->
<script type="module" src="index.js"></script>
...
...
...
</head>

@@ -165,6 +181,3 @@

import { babeInit } from './link_to_your_libraries/babe-project/babe-init.js';
import { intro, instructions, forcedChoice, thanks } from './link_to_your_libraries/babe-project/babe-views.js';
const myIntro = intro({
const intro = _babeViews.intro({
title: 'Welcome!',

@@ -176,3 +189,3 @@ text: 'This is an experiment!',

const myInstructions = instructions({
const instructions = _babeViews.instructions({
title: 'Instructions',

@@ -184,3 +197,3 @@ text: 'Choose an answer',

const main = forcedChoice({
const main = _babeViews.forcedChoice({
trial_type: 'main',

@@ -191,3 +204,3 @@ data: main_trials,

const thankYou = thanks({
const thanks = _babeViews.thanks({
title: 'Thank you for taking part in this experiment!',

@@ -198,8 +211,8 @@ trials: 1

$("document").ready(function() {
babeInit({
_babeInit({
views_seq: [
myIntro,
myInstructions,
intro,
instructions,
main,
thankYou
thanks
],

@@ -216,7 +229,6 @@ deploy: {

in: [
"practice",
"main"
],
style: "default",
width: 150
width: 100
}

@@ -230,2 +242,4 @@ });

!!!
You can also create your own views. Here is what you need to know:

@@ -251,4 +265,2 @@

import { babeInit } from './link_to_your_libraries/babe-project/babe-init.js';
const sayHello = function(info) {

@@ -306,3 +318,3 @@ const sayHello = {

$("document").ready(function() {
babeInit({
_babeInit({
...

@@ -331,3 +343,3 @@ views_seq: [

\_babe provides the option to include progress bars in the views specified in the `progress_bar.in` list passed to `babeInit`.
\_babe provides the option to include progress bars in the views specified in the `progress_bar.in` list passed to `_babeInit`.

@@ -346,3 +358,3 @@ You can use one of the following 3 styles (include pictues)

$("document").ready(function() {
babeInit({
_babeInit({
...

@@ -349,0 +361,0 @@ progress_bar: {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc