PennController for IBEX › Forums › FAQ / Tips › DashedSentence in a PennController trial
Tagged: dashe display, DashedSentence, linebreaking, newController
- This topic has 44 replies, 10 voices, and was last updated 1 month, 2 weeks ago by Jingqi.
-
AuthorPosts
-
September 23, 2019 at 2:22 pm #4220JeremyKeymaster
EDIT: PennController includes Controller elements starting with version 1.7. The preferred method now is:
Example:
newTrial( newText("instructions", "Click on the button below to start reading") .print() , newButton("Start reading") .print() .wait() .remove() , getText("instructions") .remove() , newController("DashedSentence", {s: "The mouse that the cat is chasing runs fast"}) .print() .log() .wait() .remove() , newButton("I'm done") .print() .wait() )
Option 2 below is deprecated, unless you have specific needs that cannot be addressed by the native-Ibex DashedSentence controller.
END OF EDIT
—
I am working on a general solution to inject native-Ibex-format controllers inside PennController trials.
In the meantime, this page gives you two options to present self-paced reading tasks along PennController trials.
Option 1: use the native-Ibex DashedSentence controller
Pros: you get all the features of the native controller
Cons: you don’t get the features of PennController, ie. no .log command to store metadata from your table’s columnsExample:
// This line is required for PennController experiments PennController.ResetPrefix(null) // Sequence of randomly picked pairs of test + control trials PennController.Sequence( "practice" , rshuffle("test", "control") ) // This is the standard way of defining items in Ibex var items = [ ["practice", "DashedSentence", {s: "This is a practice item"}] ] // No need to use these if uploading CSV tables to Resources/chunks_includes PennController.AddTable( "testTable" , `Sentence Hello world Bye world "Yes, or, no"`) // Note the quotes when including commas PennController.AddTable( "controlTable" , `Equation,Result1,Result2,Result3 7*7,49,53,32 489+256,745,667,233 81/9,9,7,8`) // Here we use Template to generate trials from testTable // Note that we use the Ibex-native DashedSentence controller PennController.Template( "testTable" , row => ["test", "DashedSentence", {s: row.Sentence}] ) // Here we use Template to generate trials from controlTable // Now we use a PennController trial PennController.Template( "controlTable" , row => PennController( "control" , newText("What is the result of "+row.Equation+"?") .print() , newText("Negative feedback", "Are you sure?") , // This is a dirty javascript trick to randomize the answers newScale("result", ...[row.Result1, row.Result2, row.Result3].sort(()=>0.5-Math.random()) ) .settings.labelsPosition("right") .print() // We wait until the participant selects the right answer .wait( getScale("result").test.selected(row.Result1) .failure( getText("Negative feedback").print() ) ) ) )
Option 2: code the task’s logic yourself
EDIT: this method is deprecated since PennController 1.7—Pros: you get all the features and flexibility of PennContrller
Cons: you have to recode everything from scratch…Example reproducing the default behavior of DashedSentence (self-paced reading mode and dashed display)
// This function generates an array of PennController commands // It's basically a sequence of text.settings.text() and key.wait() // The first parameter is the sentence to display (words should be separated by space characters) // The second (optional) parameter determines whether the sentence should be removed at the end // dashed = (sentence, remove) => { let words = sentence.split(' '), blanks = words.map(w=>w.split('').map(c=>'_').join('')); let textName = 'dashed'+words.join(''); // We'll return cmds: the first command consists in creating (and printing) a Text element with dashes let cmds = [ newText(textName, blanks.join(' ')).print() ]; // We'll go through each word, and add two command blocks per word for (let i = 0; i <= words.length; i++) cmds = cmds.concat([ newKey('dashed'+i+words[i], " ").settings.log().wait() , // Wait for (and log) a press on Space getText(textName).settings.text(blanks.map((w,n)=>(n==i?words[n]:w)).join(' ')) ]); // Show word if (remove) // Remove the text after the last key.wait() is parameter specified cmds.push(getText(textName).remove()); return cmds; } // Illustration // PennController( newText("instructions", "Click on the button below to start reading") .print() , newButton("Start reading") .print() .wait() .remove() , getText("instructions") .remove() , // We call the function here // Note the ... before dashed because it returns an array, which we want to inject // ...dashed("The mouse that the cat is chasing runs fast", 'remove') , newButton("I'm done") .print() .wait() )
- This topic was modified 4 years, 7 months ago by Jeremy. Reason: Added an example using Ibex-native DashedSentence
March 18, 2020 at 6:35 am #4885danielaParticipantHi Jeremy,
I’m having trouble implementing newController() inside a template. Here’s what I’ve got (I just added newController() and its commands to an already functioning script):
PennController.Template( PennController.GetTable(“fictional.csv”),
variable => [“critical”,
newController(“DashedSentence”, {s: [variable.bio], display: “in place”,blankText: “…”})
.print()
.log()
.wait()
.remove()
,
newController(“DashedSentence”, {s: [variable.pronoun,variable.verb,variable.adj,variable.objnp,variable.time,variable.spillover]})
.print()
.log()
.wait()
.remove()
)I get the following error when I try to run it: Command ‘length’ unknown on Controller element ‘DashedSentence’. (PennController: 22). I’ve updated to PC 1.7, and have also tried to figure it out in the PC trial tester with no luck.
Did I do something wrong, or does newController() not work inside a template (or both)?
Best,
DanielaMarch 18, 2020 at 9:58 am #4888danielaParticipant*Edit: I’m missing the closing ] bracket, but this was just a mistake in shortening it for the forum
March 18, 2020 at 10:18 am #4889JeremyKeymasterHi Daniela,
The Controller element, as such, is supposed to go inside a trial created with
newTrial
(previouslyPennController
). In your script, you are trying to use it like a regular native-Ibex definition, which won’t work because your newController commands appear outsidenewTrial
/PennController
. If you want to use the old Ibex definition, just build on Option 1 in my first message. If you want to use the Controller element, build on the example I gave in my Edit instead.Also, I’m not sure what you’re trying to do with your arrays of
variable.
in thes
parameter: what should happen to all those values?April 9, 2020 at 11:03 am #5030danielaParticipantHi Jeremy,
thanks for your reply. What I didn’t like about DashedSentence was the format of the results file output (it printed new column header labels for each trial–I know that would be fixed once I get my data into R, but I wanted to tidy things up for my test runs). I figured out
newController
based on your comment, but decided to just use Option 2 (recreatedDashedSentence
based on your code), and it worked great.I had arrays of
variable
in thes
parameter because I wanted to present the sentence in chunks, rather than word-by-word (each variable contained one chunk). In case anybody else stumbles upon this thread wanting to do something similar, I was able to adapt your reproduction of DashedSentence (from Option 2) by changinglet words = sentence.split(' ')
tolet words = sentence.split('*')
. Then I added an asterisk (*) to my .csv file to indicate where the chunk boundaries should be.Here’s my adaptation (including setting the font to Courier):
dashed = (sentence, remove) => { let words = sentence.split('*'), blanks = words.map(w=>w.split('').map(c=>'_').join('') ); let textName = 'dashed'+words.join(''); // We'll return cmds: the first command consists in creating (and printing) a Text element with dashes let cmds = [ newText(textName, blanks.join(' ')).print() .settings.css("font-family","courier")]; // COURIER as font // We'll go through each word, and add two command blocks per word for (let i = 0; i <= words.length; i++) cmds = cmds.concat([ newKey('dashed'+i+words[i], " ").log().wait() , // Wait for (and log) a press on Space getText(textName).text(blanks.map((w,n)=>(n==i?words[n]:w)).join(' ')) ]); // Show word if (remove) // Remove the text after the last key.wait() is parameter specified cmds.push(getText(textName).remove()); return cmds; }
and then later inside a PennController.Tempalte() I called on ‘dashed’:
...dashed(variable.critical, "remove")
Worked great, thanks!
Best,
DanielaMay 12, 2020 at 11:42 am #5231aurnhaParticipantHi Jeremy,
I created an SPR experiment following the approach outlined in your example. The words are being displayed in-place.
I realised that when the space bar is kept pressed, the all the words of the current trial are running through at max speed. It appears, the DashedSentence Controller doesn’t actually wait for a key release before registering the next keypress.
Interestingly, this also clutters the result file with many repetitions of the data from the entire sentence, but each time starting at the beginning of the sentence again, not just repeating the current word. But this might only be a tangential issue.
Is this a known problem and are you aware of any way of resolving this, e.g. by making key releases obligatory?
Thanks a lot!
ChristophMay 12, 2020 at 11:55 am #5232JeremyKeymasterHi Christoph,
Thank you for the feedback. Yes these are known problems:
– I don’t know if it makes sense to modify the native-Ibex DashedSentence controller to prevent jumping to the next word by keeping the spacebar pressed down. Or maybe I could just add a parameter that you have to set, so as to maintain retro-compatibility
– The cluttering of the results file will be fixed in the next release of PennController
Jeremy
May 13, 2020 at 9:40 am #5235mschrumpfParticipantSorry for barging into this discussion. But is there any way to keep punctuation and line breaks visible at all times in a dashed sentence trial?
Basically, an example text like this:Nico and Lotte fought together for a fair and equal world. Their collaboration proved to be extremely successful.
Nico inspired Lotte because he always wanted to take immediate action when he saw injustice.Should turn into a dashed sentence text like this:
—- — —– —— ——– — – —- — —– —–. —– ————- —— — — ——— ———-.
—- — —– —— ——– — – —- — —– —–.I could not yet figure out how to put elements into the text which would be treated as exceptions by the dashed function – nor how to concatenate several dashed sentence and plain text elements on a single trial screen.
Any help here would be greatly appreciated.
Best regards,
MatthiasMay 13, 2020 at 12:43 pm #5236JeremyKeymasterHello Matthias,
I don’t think the native-Ibex DashedSentence controller offers that option. However following option 2 from my first message, I came up with two short javascript functions that reasonably reproduce DashedSentence’s behavior:
showWord = (s,i) => '<p>'+s.map((w,n)=>` <span${(i===n?"":' style=\'border-bottom:solid 1px black;\'><span style=\'visibility:hidden;\'')}'> ${w.replace(/^\s*(\w+).*$/,"$1")}${(i===n?"":'</span>')}</span>${w.replace(/^\s*\w+/,'')}`).join(' ')+'</p>' dashed = (name, sentence) => { let words = sentence.split(' '); return [ [newText(name, showWord(words)).print()], ...words.map( (w,i) => [newKey(`${name}-${i}-${w}`," ").log().wait() , getText(name).text(showWord(words,i))] ), [newKey(`${name}-last`," ").log().wait()] ].flat(1); }
Once you’ve added that bit of code at the top of your script, you can then use it like this:
newTrial( dashed("test", "Nico and Lotte fought together for a fair and equal world. Their collaboration proved to be extremely successful.
Nico inspired Lotte because he always wanted to take immediate action when he saw injustice.") , getText("test").remove() , newButton("next").print().wait() )Just make sure you don’t insert a space before <br> otherwise it will be considered a word.
As you can see, the script creates a Text element whose name you provide as the first argument of dashed (here, test) so you can play with that element later (I decided to take it off the screen once complete, using remove).
You’ll get keypress events reported in your results file (along with a timestamp), with names like test-0-Nico, test-1-and, etc.
Incidentally, it also take care of the key-release problem that Christoph reported.
Let me know if you have any questions.
Jeremy
May 14, 2020 at 6:32 am #5247mschrumpfParticipantHello Jeremy,
thank you very much for your help. Your solution worked great. I just need to work out two more things.
Firstly, how to replace each character of each word with a hyphen “-” instead of a blank space with a border on the bottom. I’m sorry this didn’t become clearer in my other post. It looked much clearer in the editor than in the finished post.
Secondly, and perhaps more important, how to call the function in a template and supply the argument “sentence” from a row in a .csv file.
Best regards,
MatthiasMay 14, 2020 at 11:08 am #5252JeremyKeymasterHello,
You can replace the first lines from the code above (ie. until dashed = ..., non-included) with:
showWord = (s,i) => '<p style="font-family: monospace;">'+s.map((w,n)=>` <span>${w.replace(/^\s*(\w+).*$/,"$1").replace(/(.)/g,(i===n?"$1":"-"))}</span>${w.replace(/^\s*\w+/,'')}`).join(' ')+'</p>'
I used a monospace font to clearly mark each dash, but feel free to change that if you don’t like the rendering.
You can use the dashed function from within the Template command as usual:
Template( "myTable.csv" , row => newTrial( dashed("sentence" , row.sentence ) , getText("sentence").remove() , newButton("Next").print().wait() ) )
Jeremy
May 21, 2020 at 3:33 pm #5291matheusParticipantHello,
I’ve been trying to use this in a similar way to what daniela did, which is present chunks instead of single words.
However, I can’t get it to work. Where exactly should I put the code Jeremy wrote as option 2 (considering the changes daniela did to split the sentences not when there’s a space but a *)?
MatheusMay 21, 2020 at 3:45 pm #5292JeremyKeymasterHello Matheus,
You can copy the code defining the showWord and dashed functions from my message above, just replace " " with "*" in the line starting with ...words. Then use showWord as illustrated in that message, inserting a * instead of a space character in the text wherever you want to mark a chunk separation.
Jeremy
May 21, 2020 at 3:52 pm #5293matheusParticipantHello Jeremy,
Where exactly should I paste this code? In the main.js file?
I am a 0 when it comes to programming and all that, sorry.May 21, 2020 at 4:03 pm #5294matheusParticipantI think I got it, now. There was a small mistake I was doing and couldn’t understand why it was not working, but it is now. Thank you very much, Jeremy!
-
AuthorPosts
- You must be logged in to reply to this topic.