Forum Replies Created
-
AuthorPosts
-
JeremyKeymasterHello Max,
TLDR: this is the (or rather, one) correct syntax
newTrial( "experiment" , newText ("FullText", row.FullText), newButton("FTButton1","Proceed to next Trial"), newButton("FTButton2","Proceed to Comprehension Question"), newText("CompQ",row.CompQ), newButton("CQButton","Placeholder, just click to answer"), getText("FullText").print(), ( row.CompYN==Y ? getButton("FTButton2") .print() .wait() .remove("FTButton2","FullText"), getText("CompQ") .print(), getButton("CQButton") .print() .wait() .remove("CompQ","CQButton") : getButton("FTButton1") .print() .wait() .remove("FTButton1","FullText") ) ) //closes newTrialYou cannot inject if statements this way into a PennController trial, because the sequence of commands that you pass to newTrial really are arguments that you pass to a function, and javascript does not allow you to insert if statements in this kind of environment. What you can do, however, is use the so-called inline ternary conditional operator, which uses this syntax: ( test ? success : failure )
Note that the condition will be evaluated at the very beginning of your experiment, which is fine in your case because you are testing the value of a column from your table. If you wanted to test a value that is manipulated upon runtime however, say some input from your participant, you would need to use a PennController test command, to ensure that the test is performed only after input.
Jeremy
JeremyKeymasterHello Callie,
All the trials are generated at the very beginning of the experiment, along with the trial sequence, so you will not be able to dynamically select which trials to run the way you are trying to do here.
What I can suggest is a two-step design; it’s not ideal but it works well. The idea is to re-open the experiment after you participants have filled the initial form, but this time with the values they typed as parameters in the URL.
I will be assuming a table with a column race and a column sex that list the values in your experiment. For the sake of illustration, I used this command:
AddTable("practicedatasource.csv", `item,race,sex,sentence 1,Black or African American,Female,Sentence 1 for Black or African American Female 1,Asian or Pacific Islander,Female,Sentence 1 for Asian or Pacific Islander Female 1,Hispanic or Latinx,Female,Sentence 1 for Hispanic or Latinx Female 1,Other,Female,Sentence 1 for Other Female 1,Black or African American,Male,Sentence 1 for Black or African American Male 1,Asian or Pacific Islander,Male,Sentence 1 for Asian or Pacific Islander Male 1,Hispanic or Latinx,Male,Sentence 1 for Hispanic or Latinx Male 1,Other,Male,Sentence 1 for Other Male 2,Black or African American,Female,Sentence 2 for Black or African American Female 2,Asian or Pacific Islander,Female,Sentence 2 for Asian or Pacific Islander Female 2,Hispanic or Latinx,Female,Sentence 2 for Hispanic or Latinx Female 2,Other,Female,Sentence 2 for Other Female 2,Black or African American,Male,Sentence 2 for Black or African American Male 2,Asian or Pacific Islander,Male,Sentence 2 for Asian or Pacific Islander Male 2,Hispanic or Latinx,Male,Sentence 2 for Hispanic or Latinx Male 2,Other,Male,Sentence 2 for Other Male`)Then you need to define your welcome-screen-form trial to run just in case the URL does not provide you with the demographic information:
if (!GetURLParameter("id")||!GetURLParameter("race")||!GetURLParameter("sex")){ newTrial("ParticipantID", defaultText.center(),defaultDropDown.center() , newText("<p>Welcome and thank you for helping us!</p>").print() , newText("<p>Please select your race and sex. Then, please enter your ID in the box below. "+ "You can find your ID XXX, and then press enter.</p>") .print() , newDropDown("race", "Please select the race that best describes you.") .add("Black or African American", "Asian or Pacific Islander", "White", "Hispanic or Latinx", "Other") .print() .wait() , newDropDown("sex", "Please select the sex that best describes you.") .add("Female", "Male") .print() .wait() , newTextInput("inputID").center().print().wait() , newFunction( ()=>window.open("server.py?"+ "race="+getDropDown("race").value+"&"+ "sex="+getDropDown("sex").value+"&"+ "id="+getTextInput("inputID").value ) ).call() ) SendResults() newTrial("end", newText("Another window opened, you can now close this one.").print() , newButton().wait() ) }And then define the experimental trials to run upon opening the experiment’s page with the values passed as URL parameters:
if (GetURLParameter("id") && GetURLParameter("race") && GetURLParameter("sex")){ race = decodeURI(GetURLParameter("race")) sex = decodeURI(GetURLParameter("sex")) Sequence("practicesequence","etc") Template( GetTable("practicedatasource.csv") .filter("race", race) .filter("sex", sex) , row => newTrial( "practicesequence" , newText(row.sentence).print() , newButton("Next").print().wait() ) .log( "ID" , GetURLParameter("id") ) .log( "race" , race ) .log( "sex" , sex ) ) }Feel free to create two separate script files for each of these to keep your code cleaner
Jeremy
JeremyKeymasterNo as long as you have it in your scripts it will load, so you no longer need:
var HeadphoneCheckScriptTag = document.createElement("script"); HeadphoneCheckScriptTag.src = "https://s3.amazonaws.com/mcd-headphone-check/v1.0/src/HeadphoneCheck.min.js"; document.head.appendChild( HeadphoneCheckScriptTag );But maybe move this to the top of HeadphoneCheck.js:
jQuery.prototype.on = function(...args) { return jQuery.prototype.bind.apply(this, args); } jQuery.prototype.prop = function(...args) { return jQuery.prototype.attr.apply(this, args); }Jeremy
JeremyKeymasterYou can definitely do what you describe, e.g.:
PreloadZip("https://my.server/path/to/folder/archive-"+GetURLParameter("withsquare")+".zip")This will fetch file https://my.server/path/to/folder/archive-2.zip if you run your experiment with withsquare=2
Jeremy
JeremyKeymasterYou would need to upload HeadphoneCheck.js to your project and use that instead of directly loading it from S3. Then you could edit the text in HeadphoneCheck.js.
Jeremy
JeremyKeymasterHi Daniela,
I would need more detail about the whole setup of your experiment, but here is an example that picks names from a table and shows them all in a single trial in a random order:
AddTable("myTable", `name,presented John,yes Lucrecia,no Maggie,yes Vincent,yes Robert,no Shay,yes George,no __,void`) var names = []; var test; function handleNames(row){ if (row.name=="__"){ names = names.sort(v=>Math.random()>=0.5); return newTrial( ...names.map( r => { test = test || newFunction(v=>true).test.is(true); test = test.and( getScale(r.name+'-scale-'+r.presented).test.selected() ); return newText(r.name+": ") .after( newScale(r.name+'-scale-'+r.presented, 5) .before( newText(" I'm sure I NEVER saw this name ") ) .after( newText(" I'm sure I DID see this name") ) .log() .print() ) .print(); }) , newButton("Send") .print() .wait( test ) ) } names.push(row); return []; } Template( "myTable", row => handleNames(row) )I inserted the value of presented in the name of the Scale so you can retrieve the value from the results file, but I think it would be cleaner to just log the Scale score in the results file and add presented back to it from your input table when you analyze your results (in R, for example). If you have the option of defining an array in a js file rather than looking up a CSV table, as in Andrea’s case, I would do that instead, you wouldn’t need all these tricks like adding a row with __ in name.
I also had to use a trick to make sure “Send” would only proceed if all Scale elements are selected.
Jeremy
JeremyKeymasterWe can’t be sure that the delays are due to buffering, since unfortunately the result lines do not give us that information. It could be that no participant ever experienced buffering issues, but only slowdowns due to their browser’s poor performance at the time they took the experiment.
For the same reason, using the end time will not give you more accurate measures, because you don’t know what exactly caused the delay.
I realize this is a really frustrating situation, and I apologize for it—you expect the experimental software that you use to give you accurate measures, and PennController clearly failed to do so in this case. I will do my best to improve PennController’s performance.
Then again, many other factors can impact the quality of one participant’s data, including the browser they use and how they use that browser. Safari for example is known to handle media elements differently from Chrome or Firefox (which I use to develop PennController). If you see that only some participants’ data manifest this kind of delays, it could be indicative that their specific configuration (eg. using a specific browser) contributed to the delays, and hopefully you could decide whether to filter them out for analysis purposes.
Jeremy
JeremyKeymasterHi Angelica,
If you compare the play lines to the end lines, you’ll notice that you have a 0 for play where you have a “value for the length of the audio file” for end—in both cases, it actually corresponds to the position in the audio file when the event is detected. So in your case, you always have an offset of 0, which means that the play event is triggered while the audio hasn’t started playing yet.
I’m afraid that the delays you are seeing mean that execution was slow for at least some of your participants. Unfortunately, you should see lines in your results file for any buffering that happened, but I just noticed that there is a typo in the code and those won’t end up in the results file… I’ll fix this in the next release.
If you see that the delays are systematically greater for some participants, I would say this is good indication that the conditions were not optimal for those, either because of a browser slowdown (for example due to many tabs open in parallel executing various scripts) or because the audio stream that’s normally cached got lost at some point and needed to buffer again.
Jeremy
JeremyKeymasterHello Kazuko,
I have unfortunately not gotten around to enabling label display with a slider, but you can sort of work around it using a Canvas. The command size applies to every visible element, including Scale elements. The command default allows you to set the value chosen on the scale when you first display it.
Take a look a this code and let me know if you have questions:
newTrial( newScale("test", 100) .size("500px","1em") .slider() .default(0) , newCanvas("container", "500px", "2.25em") .add( "left at 0%" , 0 , newText("0%") ) .add( "center at 50%" , 0 , newText("50%") ) .add( "right at 100%" , 0 , newText("100%") ) .add( "center at 50%" , "bottom at 100%" , getScale("test") ) .print() , getScale("test").wait() )Jeremy
JeremyKeymasterHi Zoë,
You code indeed shows the feedback text for me, but the code I gave also does so. Did you make sure to update to PennController 1.8? There was a bug with testing non-waitedKey elements in prior versions.
And as you said, all of this is just to make sure you see the feedback text, but the code in this message still increments the Var element as expected (with 1.8 at least)
Jeremy
JeremyKeymasterHi Achim,
Did you make sure to place your resource files (eg. 2fishRoundTank.png) in the www folder, as mentioned here? Using chunk_includes will only work for CSV tables and HTML files.
Jeremy
JeremyKeymasterHello Max,
Keep in mind that the original Ibex DashedSentence controller already comes with a mode that lets you show one word at a time. See the Ibex documentation.
I apologize for the functions, I wrote them to be as compact as possible, but it’s not great for readability. Let me rewrite them more clearly:
showWord = (arrayOfWords,showIndex) => "<p style='font-family: monospace;'>" + arrayOfWords.map( (word,n) => { const letters = word.replace(/^\s*(\w+).*$/,"$1"); // this only keeps letters (and numbers) const punctuation = word.replace(/^\s*\w+/,""); // this only keeps the punctuation characters if (n==showIndex) return "<span>"+letters+"</span>"+punctuation; else return "<span style='visibility: hidden;'>"+letters+"</span>"+punctuation; } ).join(" ") + "</p>";dashed = (name, sentence) => { const words = sentence.split(" "); // Use space to break string into array return [ // Return an array of key.wait + text.print commands [ newText(name,showWord(words)).print() ], // First reveal no word ...words.map( (word,index) => [ newKey(name+"-"+index+"-"+word," ").log().wait() , getText(name).text( showWord(words,index) ) // reveal INDEXth word ]), [ newKey(name+"-last"," ").log().wait() ] ].flat(1); }As you can see, it’s the regular experessions in showWord that take care of separating the punctuation characters from the rest.
Jeremy
JeremyKeymasterHi,
You can edit your files locally in a text editor, make sure to save them with a utf-8 encoding and then upload them to the PCIbex Farm, or replace your characters with HTML entities like: ó ñ etc.
A list here.
Jeremy
JeremyKeymasterHi Sander,
If you go back to my first message, you will see that I edited it after introducing the Controller element in PennController 1.7. So you should probably try this:
Sequence( "practice" , rshuffle("trial") ) newTrial("practice", newController("DashedSentence", {s: "This is a practice item so that you know what the task looks like"}) .print() .wait() ) Template( "CSV_Template.csv", row => newTrial( "trial" , newController("DashedSentence", {s: row.Sentence}) .print() .wait() .remove() , newText("CompQ", row.CompQ) .print() , newText("Negative feedback", "Not correct!") , // This is a dirty javascript trick to randomize the answers newScale("result", ...[row.Results1, row.Results2].sort(()=>0.5-Math.random()) ) .labelsPosition("right") .print() // We wait until the participant selects the right answer .wait( getScale("result").test.selected(row.Results1) .failure( getText("Negative feedback").print() ) ) ) )Jeremy
JeremyKeymasterHello,
PennController is activated as soon as you add PennController.js to the js_includes folder. If you didn’t change anything in the main script file of your experiment, you won’t see any difference, as PennController should be 100% compatible with native Ibex.
Jeremy
-
AuthorPosts