Forum Replies Created
-
AuthorPosts
-
Jeremy
KeymasterSome errors are not fatal: as long as you know what triggers them (eg. the intentional absence of a Group column in the filler table) but you are still able to run the experiment and collect the data you need, it’s ok to ignore them (your participants won’t see them once you’re ready for data collection and use DebugOff()). Just make sure you don’t overlook other errors when skipping them over.
Jeremy
Jeremy
KeymasterHello,
You are passing three arguments to the trial log command, when it should take only two: the first argument is the name of the column to be added, the second argument is the value of that column on each trial row in the results file.
If you simply get the participant’s id by looking up the URL (as opposed to, say, asking them to type it in a field) then you don’t need to use a Var element, just use:
.log( "ParticipantID" , GetURLParameter("id") )
Jeremy
Jeremy
KeymasterHi,
The solution should indeed be the same—as you saw on the other topic, the table used for the filler items does not have a group column and yet it just works. You just want to make sure you use two Template commands to use the two different tables, as illustrated on the other topic.
Jeremy
Jeremy
KeymasterHi Angelica,
You can always use regular javascript alongside PennController commands. So you can do this:
customTrial = label => variable => newTrial( label , defaultText .center() , // Display target word newText("listen_for", "Listen for:") .css({"width": "300px"}) .italic() , newText("target_word", variable.target) .css({"font-size": "200%", "width": "300px"}) , newText("next", "Press the spacebar to continue.") .css({"width": "300px"}) .italic() , newCanvas(300, 300) .add(0, 100, getText("listen_for")) .add(0, 150, getText("target_word")) .add(0, 250, getText("next")) .print() , newKey(" ") .wait() , clear() , // Play audio stimulus and record reaction time newImage("fixation_cross", "fixation_cross.png") .size(300, 300) , newCanvas(300, 300) .add(0, 0, getImage("fixation_cross")) .print() , newKey("reaction_time", " ") .log("all") , newAudio("stimulus", variable.stimulus) .play() .wait() , clear() , // Comprehension question (variable.question?[newText("question", variable.question) .cssContainer({"width": "600px", "font-size": "150%", "height": "50px"}) , newText("answer_1", variable.answer_1) .cssContainer({"width": "300px"}) , newText("answer_2", variable.answer_2) .cssContainer({"width": "300px"}) , newCanvas(600, 300) .add(0, 150, getText("question")) .add(0, 200, getText("answer_1")) .add(300, 200, getText("answer_2")) .print() , newKey("FJ") .wait() .log() ]:null) ) .log("group", variable.group) .log("item", variable.item) .log("condition", variable.cond2) .log("target", variable.target) .log("correct", variable.correct)
Then you can use it like this:
// experimental item trial Template("test_items", customTrial("items") ) // Filler item trial Template("test_fillers", customTrial("fillers") )
Jeremy
Jeremy
KeymasterGreat!
If anyone else is having the same issue, one solution is to inject some HTML in your sentence, for example: Please identify the last <span style='color: red; font-weight: bold;'>sound<span>.
Jeremy
Jeremy
KeymasterHello,
Yes, you can use .test.text to check the content of a TextInput element, and doing so in the wait command will only validate the click if the test is successful. In your case, you can use regular expressions to check for appropriate numbers, just replace your very last .wait() line with this:
.wait( getTextInput("age").test.text( /^(\d|1[0-8])$/ ) .failure( newText("Age should be a number between 0 and 18").print() ) .and( getTextInput("weight").test.text( /^\d+$/ ) .failure( newText("Weight should be a numeric value").print() ) ).and( getTextInput("height").test.text( /^\d+$/ ) .failure( newText("Height should be a numeric value").print() ) ) )
Jeremy
Jeremy
KeymasterHi Mieke,
You could test __counter_value_from_server__ in your Template command and assign labels accordingly: Template( row => newTrial( __counter_value_from_server__%2?"secondblock":"firstblock" , /* ... */ ) )
Note: __counter_value_from_server__ is only defined after your script has been evaluated, so you cannot use it outside Template (whose execution is delayed to fetch the tables)
Jeremy
Jeremy
KeymasterHi Mieke,
Yes, you can definitely accomplish what you want with PennController, but I think you’d need to take a slightly different approach. Let me give you an example, starting with a table of this format (of course you can use a CSV file directly instead of using AddTable):
AddTable("myTable", `Item,Sentence,Group 0,tree lost its leaves,possessive 0,tree fell on itself,reflexive 1,pond had its water drained,possessive 1,pond replenished itself,reflexive 2,cloud kept its shape,possessive 2,cloud stood by itself,reflexive 3,rock had its top smoothed,possessive 3,rock spun on itself,reflexive 4,hill hid a cave at its top,possessive 4,hill hid on itself,reflexive`)
Here I have 5 trials, numbered from 0 to 4, which appear in one of two conditions (possessive vs reflexive) every other time the experiment is run, because of the Group column. Nothing crucial relies on this, it’s just for the sake of illustration.
Now I’ll define some variables in the script to configure the design you describe:
firstBlockLength = 2 secondBlockLength = 3 primes = "EU" firstBlockQuantifiers = "EU" secondBlockQuantifiers = "EUN"
I’m saying: there will be 2 trials in the first block and 3 trials in the second (you would replace those numbers with 30 and 24 instead). I’m also saying: each prime (regardless of block) will be either ‘E’ (for existential) or ‘U’ (for universal), and each in-sentence quantifier in the first block will be either ‘E’ (to be replaced with the determiner “A”) or ‘U’ (to be replaced with “Every”) and each in-sentence quantifier in the second block will be ‘E,’ ‘U’ or ‘N’ (to be replaced with “No”).
Note: the numbers in my example are not ideal, since the second block has 3 trials over which you cannot properly distribute 2 prime types—just make sure the number of trials in each block is a multiple of the number of prime types and the numbers of quantifiers in both trials; 24 and 30 both being multiples of both 2 and 3, you should be fine
Now here’s some javascript magic to automatically associate your trials with the desired conditions:
// Generates a random, even array of LENGTH characters picked from a string randomArray = (length,list) => list.split('').map(v=>v.repeat(parseInt(length/list.length))) .flat(1).sort(v=>Math.random()>=0.5) listOfPrimesFirst = randomArray(firstBlockLength,primes) listOfPrimesSecond = randomArray(secondBlockLength,primes) listOfQuantifiersFirst = randomArray(firstBlockLength,firstBlockQuantifiers) listOfQuantifiersSecond = randomArray(secondBlockLength,secondBlockQuantifiers) translator = {E: "One", U: "Every", N: "No"} // Randomly order the indices first indices = [...new Array(5)].map((v,i)=>i).sort(v=>Math.random()>=0.5) // Now assign properties to trials trials = [] listOfQuantifiersFirst.map( (v,i) => trials[indices[i]] = { block: "first", prime: translator[listOfPrimesFirst[i]], quantifier: translator[v] } ) listOfQuantifiersSecond.map( (v,i) => trials[indices[firstBlockLength+i]] = { block: "second", prime: translator[listOfPrimesSecond[i]], quantifier: translator[v] } )
Once this is in place, you can use Template and GetTable().filter to generate trials:
Template( // Only use the rows where Item appears in the indices of the first block GetTable("myTable").filter( row=>indices.slice(0,firstBlockLength).indexOf(Number(row.Item))>=0 ) , row => newTrial( 'firstblock' , newText("space","Please press Space").print(), newKey(" ").wait(), getText("space").remove() , newText( "prime" , trials[row.Item].prime ).log().print(), newTimer(20).start().log().wait(), // 20ms prime getText("prime").remove() , newController("DashedSentence", {s: trials[row.Item].quantifier+' '+row.Sentence}) .print().log().wait().remove() ) .log( "Group" , row.Group ) .log( "Block" , trials[row.Item].block ) .log( "Sentence" , row.Sentence ) .log( "Prime" , trials[row.Item].prime ) .log( "Quantifier" , trials[row.Item].quantifier ) ) Template( // Only use the rows where Item appears in the indices of the second block GetTable("myTable").filter( row=>indices.slice(firstBlockLength,).indexOf(Number(row.Item))>=0 ) , row => newTrial( 'secondblock' , newButton("Please click here").print().wait().remove() , newText( "prime" , trials[row.Item].prime ).log().print(), newTimer(10).start().log().wait(), // 10ms prime (more-or-less, hardware constraints) getText("prime").remove() , newController("DashedSentence", {s: trials[row.Item].quantifier+' '+row.Sentence}) .print().log().wait().remove() ) .log( "Group" , row.Group ) .log( "Block" , trials[row.Item].block ) .log( "Sentence" , row.Sentence ) .log( "Prime" , trials[row.Item].prime ) .log( "Quantifier" , trials[row.Item].quantifier ) )
Let me know if you have questions about adapting this to your case
Jeremy
Jeremy
KeymasterHello,
You could do something like this:
Template( "myTable.csv" , row => newTrial( "experiment" , newController("DashedSentence", {s: row.Sentence}).print().log().wait() .remove() , (row.Question?[ newText("Question", row.Question).print() , newKey("FJ") .wait() .log() ]:null) , [etc]
Jeremy
Jeremy
KeymasterHi Sam,
EDIT: actually you don’t have to use the Function element as I suggest below, but you’ll still need a little bit of javascript when setting and testing your Var element:
Template( "fulldesign.csv" , variable => newTrial( "task" , newVar("fullstring", "") , newText("Please type your guess").print() , newKey("guess", "") .callback( getVar("fullstring").set( v=>v+getKey("guess").value ) ) , newKey("Enter").wait() , getVar("fullstring").test.is( v=>v.match(new RegExp(variable.answer+"$","i")) ) .success( newText("Correct!").print() ) .failure( newText("Incorrect").print() ) , newButton("next").print().wait() ) )
End of edit
I think you’ll have to inject some javascript in there, using the Function element:
Template( "fulldesign.csv" , variable => newTrial( "task" , newText("Please type your guess").print() , newFunction(function(){ this.fullstring = ""; let oldOnkeydown = document.onkeydown; document.onkeydown = e=>{ if (oldOnkeydown instanceof Function) oldOnkeydown.call(document, e); this.fullstring += e.key; }; }).call() , newKey("Enter").wait() , newFunction(function(){ return this.fullstring.match(new RegExp(variable.answer+"$","i"))!=null; }).test.is( true ) .success( newText("Correct!").print() ) .failure( newText("Incorrect").print() ) , newButton("next").print().wait() ) )
Two comments on your code:
- The setVar command (as all PennController commands) is executed immediately when the script encounters it, so unless it is in a callback command, it will only be executed once. In your case, you call it immediately after creating the Key element, so your Var element will remain empty.
- The after command adds an element to the right of another element when printed on the page, but it does not change the value of either element
Jeremy
-
This reply was modified 5 years, 2 months ago by
Jeremy. Reason: Added a Function-less script
Jeremy
KeymasterHello Gemma,
This is really weird, it’s an error that comes from an old version of the PHP script from this page, I remember fixing it (on August 15, 2019 according to the logs). Did you make sure your PHP script is up to date?
Jeremy
Jeremy
KeymasterThat’s because the counter value from the URL is interpreted as a string but the switch statement tests for numbers. Use Number(GetURLParameter("withsquare")) to make sure the counter value from the URL is interpreted as a number.
Jeremy
Jeremy
KeymasterNo difference, I was just trying to make the forum’s syntax-highlighter happy
Jeremy
KeymasterHello!
1. Everything looks OK as far as I can tell, but I think you should be able to test SONA’s credit granting if you can create a dummy account. At least this is what I did in the past, and I setup my SONA projects to only accept my dummy account as a participant (maybe I also password-protected them?) and I was able to do a proper testrun
2. Do you mean that the credit token will be different for different SONA experiments? I forget. If so, you could check the value of withsquare from the URL at the top of your script and set a variable accordingly that you use to generate your link:
credittoken = ""; switch (GetURLParameter("withsquare")) { case 0: credittoken = "hellothere"; break; case 1: credittoken = "byenow"; break; case 2: credittoken = "salutsalut"; break; case 3: credittoken = "bonbahaplus"; break; } // ... newText('Click here to confirm my submission on SONA.')
Jeremy
Jeremy
KeymasterHello Sam,
I see how things become tricky here: in order to reach the end of the trial, you will need to validate the Button’s wait command. This is fine as long as you give the correct answer, because then the test on your TextInput is a success. However, you also want to end your trial after 3 wrong answers, which means validating the wait, but we’ve already established that this situation corresponds to a failed test on your TextInput, so your wait command won’t be validated!
One solution here is to not call wait on this specific button, but on another one instead, and handle your tests in a callback instead. I’m pasting a full functional script below: I slightly rearranged your code to my preferences, but of course feel free to move things back to how they were. I also updated the command names to reflect the changes in PennController 1.7. And finally, the AddTable bit is here just to create a table named fulldesign.csv in case someone wants to test the code who doesn’t have an appropriate table (as was my case).
PennController.ResetPrefix(null) Sequence( "start", "task", "end" ) newTrial("start", newButton("start").print().wait()) newTrial("end", newButton("end").print().wait()) AddTable("fulldesign.csv", `sen,answer,n what's 2+2?,4,1 what's my cat's name?,buffy,2 is this fun?,so-so,3`) Template( "fulldesign.csv" , variable => newTrial( "task" , newVar("attempt").global() .test.is(3).failure( // beginning of Var test newText("warning", "Your guess shouldn't be empty") , newText(variable.sen).print() , newText("input","Enter your guess: ") .after(newTextInput("guess").log("final")) .print() , getVar("attempt").set(0) , newButton("bb", "Continue") .print() .callback( getTextInput("guess").test.text(variable.answer) .failure( getTextInput("guess").test.text("") .success(getText("warning").print()) .failure( getVar("attempt").set( v => v+1 ).test.is(3) .success( getButton("bb").disable(), getButton("next").print() ) ) ) .success( getButton("next").click() ) ) , newButton("next", "Sorry, you failed 3 times") .wait() // end of Var test ) ) .log( "ParticipantID", GetURLParameter("id") ) .log("trial", variable.n) )
Let me know if you have any questions
Jeremy
-
AuthorPosts