Forum Replies Created
-
AuthorPosts
-
JeremyKeymasterHi Jun,
What version of PennController are you using? Also, what does your HTML file look like?
Using PennController 2.0 and this HTML code, things work just fine for me:
<script> function newCode() { const randomCode = [...Array(8)].map(()=>Math.round(Math.random()*10)).join(''); document.querySelector("#code").value = randomCode; document.querySelector("#codeDisplay").innerText = randomCode; } </script> <button onclick="newCode()">Generate code</button> <input id='code' type='text' name='code' style='display:none;' class='obligatory' /> <p>Your code is <span id='codeDisplay'></span></p>Jeremy
JeremyKeymasterHi Ebru,
You could get there by using the Selector element, if you created one per cell, but the code would quickly become cumbersome. A more concise solution would be to use three Scale elements, one per row, that you set as checkboxes. Then you manipulate the aesthetics to make a checked option appear as a black square, and an unchecked option as a white square
The test part in
waitis a little tricky, because up to PennController 2.0,.test.selecteddoes not behave as expected on a checkbox Scale. However, one can write some concise javascript code to test that the right boxes are (un)selectedHere’s the script:
Template("dotmemorization.csv", row => newTrial("dot-pattern", defaultScale.checkbox().center().log().print() , newScale("row1", " ", " ", " "),newScale("row2", " ", " ", " "),newScale("row3", " ", " ", " ") , getScale("row1").select(row.r1c1==1?0:-1).select(row.r1c2==1?1:-1).select(row.r1c3==1?2:-1), getScale("row2").select(row.r2c1==1?0:-1).select(row.r2c2==1?1:-1).select(row.r2c3==1?2:-1), getScale("row3").select(row.r3c1==1?0:-1).select(row.r3c2==1?1:-1).select(row.r3c3==1?2:-1) , newTimer(850).start().wait() , getScale("row1").unselect(0,"log").unselect(1,"log").unselect(2,"log").remove(), getScale("row2").unselect(0,"log").unselect(1,"log").unselect(2,"log").remove(), getScale("row3").unselect(0,"log").unselect(1,"log").unselect(2,"log").remove() , newText('inst', 'Now produce the pattern').css("margin-bottom","20px").print().center() , getScale("row1").print(),getScale("row2").print(),getScale("row3").print() , newButton("press") .print() .wait( newFunction( ()=>[...document.querySelectorAll(".PennController-Scale input")].map(c=>Number(c.checked)).join('') ) .test.is([row.r1c1,row.r1c2,row.r1c3,row.r2c1,row.r2c2,row.r2c3,row.r3c1,row.r3c2,row.r3c3].join('') ) .failure( newText("Try again!").print() ) ) .center() ) )And here’s what to put in global_main.css to make the checkboxes appear as white and black boxes:
.PennController-row1 input, .PennController-row2 input, .PennController-row3 input { display: none; } .PennController-row1 input + label, .PennController-row2 input + label, .PennController-row3 input + label { display: inline-block; width: 40px; height: 40px; background-color: white; border: solid 1px grey; } .PennController-row1 input:checked + label, .PennController-row2 input:checked + label, .PennController-row3 input:checked + label { background-color: black; }Jeremy
JeremyKeymasterHi,
I apologize for the issue: I brought some modifications to the server’s configuration yesterday and mistakenly cut communication between the farm and the database
Things should be back to normal now
Jeremy
JeremyKeymasterHi,
My bad, the code should have been
newTimer("callback",1) .callback( getAudio("audio").wait().remove() , getText("replay").remove() ) .start()Jeremy
-
This reply was modified 2 years, 8 months ago by
Jeremy. Reason: fix order
JeremyKeymasterHi,
1) You can not use
getVarinside anewXcommand. Instead, you need to use it inside the Text element’stextcommand, like this:newText("sentence", "").text( getVar("segmentation") )2) The easiest solution would be to create as many TextInput elements that you will allow the user to add, and dynamically add/remove them to/from the page. Here is an example with the possibility of adding up to 5 extra input boxes: https://farm.pcibex.net/r/kZmhKa/
Jeremy
JeremyKeymasterHi Laia,
Your code actually works pretty well already, the only problem is that the text on the left overflows onto the text on the right. This is because you didn’t explicitly set a width to the text element, so it inherits the width of its container, the Canvas element
Just give an explicit width to your Text elements and you’ll be good:
getText(oneFirst?"sentence1":"sentence2").size(300,"auto").print( 20,0,getCanvas("frases")), getText(oneFirst?"sentence2":"sentence1").size(300,"auto").print(350,0,getCanvas("frases")),Jeremy
JeremyKeymasterHi,
Remove the Text element in the same callback, and don’t
waitfor it (you cannotwaitfor Text elements):newTimer("callback",1) .callback( getText("replay").remove() , getAudio("audio").wait().remove() ) .start()Jeremy
January 22, 2023 at 2:59 pm in reply to: Presenting self-defined regions in self-paced reading #9853
JeremyKeymasterHi,
PennController is a simple extension to IBEX, so you can still do what you used to do. For example, this is still valid:
items = [ ["label", "DashedSentence", {s: ["This is", "what", "I", "have", "in mind"]}] ];However, you can only store strings in a CSV file, you cannot directly store a javascript array (which is what
["This is", "what", "I", "have", "in mind"]is) so to accomplish the same using a string from a CSV file, you need to split it into an array, using a character of your choice to serve as the splitting character. Say you choose to use ~, then you can haveThis is~what~I~have~in mindin your CSV file, and then do this in yourTemplatecommand:Template( row => newTrial( newController("DashedSentence", {s: row.sentence.split("~")}) .print() .log() .wait() ) )Jeremy
JeremyKeymasterHi Yev,
PennController always gives focus to the most recently added TextInput element. If at one point in your trial, you want focus to go to a given TextInput element, you can use the Function element to execute some javascript code during runtime:
newTrial( newTextInput("first").print() , newTextInput("second").print() , newFunction( ()=>document.querySelector(".PennController-first").focus() ).call() , newButton("Next").print().wait() )Jeremy
JeremyKeymasterHi Nico,
As long as you have the exact same list of groups in your two tables, you can use a
Templatecommand for one table and another command for the other table, and participants will be assigned the same group across tables. So this is how I would do things:Template("Mix.csv", row => newTrial("Global", // etc. Template("CT.csv", row => newTrial("Random", // etc. Sequence( ...( Math.random() > 0.5 ? [ randomize("Global") , randomize("Random") ] : [ randomize("Random") , randomize("Global") ] ) )Jeremy
January 17, 2023 at 2:38 pm in reply to: Randomizing the sentences in a self-paced reading experiment #9846
JeremyKeymasterHello,
If you want to use the method in that project, you would just need to repeat the lines from
var count=2;tonewArray.push(originalArray[i]);using new variable names (eg.count2,newArray2,originalArray2) and in yournewTrialcommand, reference the newnewArrayvariable in place of the sentences followed by a question, just like you’re currently doing with the other ones, eg.newController("DashedSentence", {s : newArray2[0]})However you may be interested in creating one trial per sentence(/question) instead of including them all in a single trial. To do so, you would list your sentences and questions in a CSV file and use
Templateto create as many trial as you have rows in the CSV file. You can conditionally include a question in the trial by looking up the value of a column, as described in this post. Then you can simply userandomizeorrshuffleinSequenceto randomize the order in which the sentences(+questions) appearJeremy
January 11, 2023 at 6:45 pm in reply to: Pseudorandomization with no two items of the same type in a row #9840
JeremyKeymasterHi Dari,
The method used by the function in that post is too brute force to efficiently return a sequence every time the experiment is run
Use this instead:
function rnmt(toFill,fillFrom,n){ if (fillFrom.length<1) return toFill; let lst = toFill[toFill.length-1], nxt = fillFrom[0]; if (toFill.length>=n) { if (nxt.type == lst.type) { let lstN = 0; for (let i=toFill.length-1; i>=0 && toFill[i].type==lst.type; i--) lstN += 1; if (lstN>=n) { for (let i=0; i<fillFrom.length && nxt.type==lst.type; i++) { fillFrom = [...fillFrom.slice(1,),fillFrom[0]]; nxt = fillFrom[0]; } if (nxt.type==lst.type) return false; } } } return rnmt([...toFill,nxt],fillFrom.slice(1,),n); } function RandomizeNoMoreThan(predicate,n) { this.args = [predicate]; this.run = function(arrays) { let ret = false; while (!ret){ fisherYates(arrays[0]); ret = rnmt([],[...arrays[0]],n); } return ret }; } function randomizeNoMoreThan(predicate, n) { return new RandomizeNoMoreThan(predicate,n); }Jeremy
JeremyKeymasterHi,
You have two options. One is to use one column per sentence, so if you have two sentences you need two columns instead of one for the sentences in your CSV file. Let’s say you name your first column text_1 and the second column text_2, then you would do
newController("DashedSentence", {s : [row.text_1, row.text_2]})Another option is to use javascript to split the string coming from the table into multiple sub-strings. Assuming you are separating your sentences with the comma character (meaning no comma will ever occur as an actual punctuation in a sentence) then you would proceed like this:
newController("DashedSentence", {s : row.text_1.split(',') })Jeremy
January 6, 2023 at 4:12 pm in reply to: adding comprehension quesiton to self paced reading task #9836
JeremyKeymasterHi Doğan,
Apologies for the late reply. The native Ibex controller called DashedAcceptabilityJudgment readily provides these two options. Here’s an example of a PennController trial using it:
newTrial( newController("DashedAcceptabilityJudgment", { s: "While Alex was watching TV, Sue was washing the dishes", mode: "self-paced reading", q: "Who was watching TV?", as: ["Alex", "Sue"] }) .log() .print() .wait() )Jeremy
JeremyKeymasterHi Emiel,
If you want to prevent the experiment from sending the results when the recordings fail to upload (even though the two things are separate, and as you found out results can still be saved even though recordings aren’t uploaded) then you can do something like this:
Sequence( "init", sepWith("async", rshuffle("letter","picture")), "sync", "last" ) UploadRecordings("sync") newTrial("last", newFunction("check upload", ()=>PennController.UploadRecordingsError) .test.is(undefined) .failure( newText("error", "There was a problem sending the recordings to the server. ") .color("red") .print() , newButton().wait() ) )The idea is you manually insert a synchronous
UploadRecordingsin yourSequenceand check in the next trial whetherPennController.UploadRecordingsErrorreports an error: if so, then you usenewButton().wait()to prevent the trial from completing and proceeding to the end of the experiment (=sending the results)Jeremy
-
This reply was modified 2 years, 8 months ago by
-
AuthorPosts