Forum Replies Created
-
AuthorPosts
-
JeremyKeymasterHi,
First let me bring your attention to what I said at the beginning of this message, repeated here:
Template scans the table and produces one output per row. So the idea is, you use Template( row => newTrial to generate one trial per row. Listing one word per row is not the ideal way to go if you want to display several of them at the same time on the page, ie. during the same trial
If you want to display several words on one screen, listing them on different lines in a CSV file is not the way to go
One way to implement what you describe is to proceed very similarly to what we discussed in the other thread: instead of incrementing the value of the Var element named “nextWordStartsAt” by 10 on each keypress and replacing the text of the Text element named “words” with the next 10 words, you increment the value by 1 each time and replace the text with the same list of words each time, just edited to place an HTML tag around the next word each time
So it could look something like this, assuming you always list 9 words in your table’s “Words” column (you could do 12 words as in your example too, just adjust the widths and heights accordingly to fit 4×4 words):
// embed each word in a span, highlight the very first one newText("words", row.Words.split('_').map((w,i)=>`<span class='word ${i==0?'highlighted':''}'>${w}</span>`).join('')) .cssContainer("width","810px") // 3 times the space reserved for each word + horizontal padding .css({ display: 'flex', // items contained in the element will be rearranged smartly 'flex-direction': 'column', // stack items in columns 'flex-wrap': 'wrap', // items that overflow vertically will start on new columns 'font-size': '50px', height: '210px' // more than 3 times the space reserved for each word + vertical padding }) .print() , newVar("listOfWords", ""), // we'll update this Var element with the list of words tagged as desired newVar("highlightedWord").set(0) // this is the index of the highlighted word , newKey("NEXT", " ") .callback( // first make sure that the next index is within bounds getVar("highlightedWord").test.is(v=>v+1<row.Words.split('_').length).success( getVar("highlightedWord").set(v=>v+1) // increment the index of the highlighted word , getVar("listOfWords") // update listOfWords with appropriate tags .set(getVar("highlightedWord")) // first look up the index .set(v=>row.Words.split('_').map((w,i)=>`<span class='word ${i==v?'highlighted':''}'>${w}</span>`).join('')) // now set the class as desired , getText("words").text( getVar("listOfWords") ) // update the content of the Text element ) )In global_main.css, you add these rule:
.PennController-words .word { width: 230px; height: 60px; padding: 5px 15px; } .PennController-words .word.highlighted { outline: solid 10px yellow; border-radius: 0.25em; }Just make sure the width you pass in your script to
.cssContaineris at least 3 times (the width + 2*padding) and the height you pass to.cssis at least 3 times (the height + 2*padding), that you set in the file’s CSS rule for.word.Jeremy
JeremyKeymasterHi,
I don’t immediately see a problem with your code. Would you be able to share a link to your experiment with me (even if it’s not on the PCIbex Farm), here or at support@pcibex.net so I can take a closer look?
Jeremy
JeremyKeymasterHi Juliana,
Unfortunately the Question controller was written in a way that it reports the content of the selected answer, not its index. However, since you are logging the content of both questions, you can compare the content of the answer to those columns and determine the index of the chosen answer, which you can then compare to the expected index. For example, here is a line from a test-run of mine, with a header line with column names before it for clarity:
ReceptionTime,MD5,Controller,Item,Element,Label,Group,PennElementType,PennElementName,Parameter,Value,EventTime,Frase1,Frase2,Condition,Item,Group,Expected,Correct,Time,Comments 752,REDACTED,PennController,11,0,item,NULL,Controller-Question,Question,NULL,Ou hoje ou amanhã fomos à praia.,1684831742806,Ou hoje ou amanhã iremos à praia.,Ou hoje ou amanhã fomos à praia.,TREINO,1,U,1,NULL,1967
In R for example, and assuming
resultsonly contain rows for that Question controller, I would do something like this:results$AnswerIndex <- 1 + (results$Value == results$Frase2) results$Accurate <- results$AnswerIndex == results$Expected
Note, however, that the Question controller already comes with a
hasCorrectoption, so you could donewController("Question", {q: "", as: [variable.Frase1, variable.Frase2], hasCorrect: parseInt(variable.Expected)-1}). Then you'd have a 1 reported for correct answers and a 0 for incorrect answersJeremy
JeremyKeymasterHi,
You have two problematic file references in your table:
- 34c_schoolbuls.mp3
- 36c_tableware.mp3
The former contains a typo (which I highlighted in boldface) the latter is absent from the zip file
Jeremy
May 19, 2023 at 12:21 pm in reply to: Randomize blocks and trials but keep breaks in the same order #10603
JeremyKeymasterIn that case use two
randomizes inseq, like this:var blocks = [ seq(randomize("trials_study"),randomize("trials_test")), // ...Jeremy
May 19, 2023 at 11:03 am in reply to: Randomize blocks and trials but keep breaks in the same order #10601
JeremyKeymasterHi Yev,
If your first block consists of all the trials labeled “trials_study” and those named “trials_test” and you want them randomly (unevenly) shuffled, you could replace
randomize("block_1")from the code above withrandomize(anyOf("trials_study","trials_test")). If you’d rather have them evenly shuffled, usershuffle("trials_study","trials_test")Jeremy
JeremyKeymasterHello Lorrainy,
The error messages are correct: your table contains no columns named “Pergunta” or “Frase”. They do, however, contain two columns named “pergunta” and “frase”
That being said, your table uses
;as a separator, which is not currently supported by PennController. Replace them with,(commas) and, along with the two edits I just hinted at, your experiment will run smoothlyJeremy
JeremyKeymasterHello,
You’re using
AddHostin place ofPreloadZipAlso delete the trailing slash after
.zipin the zip file’s URLJeremy
JeremyKeymasterYou could test the value of the Var element right after you increment it in the
callbackto see if the updated index goes beyond the number of words, and if so, wait for a key press on K to stop the timer:newKey("NEXT", " ") .callback( getVar("nextWordStartsAt").test.is(v=>v<row.Words.split('_').length).success( getVar("listOfWords") .set(getVar("nextWordStartsAt")) .set(v=>row.Words.split('_').slice(v,v+10).join("<br>")) , getText("words").text( getVar("listOfWords") ) , getVar("nextWordStartsAt") .set(v=>v+10) // update the index .test.is(v=>v>=row.Words.split('_').length).success( // if the index is greater than the # of words newKey("K").wait() // wait for a key press on K , getTimer("recording").stop() // stop the timer ) ) )Jeremy
JeremyKeymasterHi,
Just add
.failure( getTimer("recording").stop() )to the test on the index for the next 10 words:newKey("NEXT", " ") .callback( getVar("nextWordStartsAt").test.is(v=>v<row.Words.split('_').length).success( getVar("listOfWords") .set(getVar("nextWordStartsAt")) .set(v=>row.Words.split('_').slice(v,v+10).join("<br>")) , getText("words").text( getVar("listOfWords") ) , getVar("nextWordStartsAt").set(v=>v+10) ) // stop the timer if all the words have been exhausted .failure( getTimer("recording").stop() )Jeremy
JeremyKeymasterHello Shayne,
My bad, the command is
.consent, not.setConsentJeremy
JeremyKeymasterHi Hailin,
You could use the custom
pickfunction. Assuming all your critical items have labels that start withexp.and all your filler items have labels that start withfil.it would look something like this:var shuffleSequence = seq( shuffle( pick( randomize(startsWith("exp.")) , 10), pick( randomize(startsWith("fil.")) , 30) ) )Jeremy
JeremyKeymasterHi,
In that case what I would do is list all the words in a single cell (named “Words” for example) and use Var elements to dynamically update the content of the Text element upon pressing the key:
Template("new--Items_Wide.csv", row => newTrial("listOfWords", fullscreen() , newMediaRecorder("recorder", "audio").log().record() , newTimer("recording", 3000).log().start() , newText("words", row.Words.split('_').slice(0,10).join("<br>")) // use slice to get the 10 first words .cssContainer({ padding: '0.5em', 'text-align': 'center', "justify-content": 'center', "align-items": 'center' , 'font-size': '50px', }) .print() , newVar("listOfWords", ""), // we'll set this Var element to the next list of words newVar("nextWordStartsAt").set(10) // this is the index where the next 10 words start , newKey("NEXT", " ") .callback( // first make sure that the index is within bounds getVar("nextWordStartsAt").test.is(v=>v<row.Words.split('_').length).success( // set listOfWords to the next words getVar("listOfWords") .set(getVar("nextWordStartsAt")) // first look up the index .set(v=>row.Words.split('_').slice(v,v+10).join("<br>")) // then use the index+slice to get the next 10 words , getText("words").text( getVar("listOfWords") ) // update the content of the Text element , getVar("nextWordStartsAt").set(v=>v+10) // the next list of 10 words starts 10 words further ) ) , getTimer("recording").wait() , getMediaRecorder("recorder").stop() , getText("words").remove() , newText('time-is-up-msg', "Time is up!").print() , newButton('btn--finish--game', 'Finish Game').print().wait() ) )Jeremy
JeremyKeymaster[ EDIT : changed
setConsentforconsent]Hi,
You can use
.consentonInitiateRecorder()to change the consent message:InitiateRecorder("https://myserver/uploadVoices/saveRecordings.php" , "<b><center>RESEARCHER, click on the button below to <b style=color:red;>authorize</b> audio recording in the browser 👇</b></center>" ).label("first-trial") .consent("I Consent")Regarding the error message, you could use the hack described in this post to replace the message
Jeremy
-
This reply was modified 3 years ago by
Jeremy. Reason: changed setConsent for consent
JeremyKeymasterHi,
You cannot change the error message, as it is hard-coded in the MediaRecorder element. What you are doing is create a new trial, and printing a Text element in that trial depending on whether the upload succeeded or failed (note that the trial needs to come after the upload attempt for
uploadRecordingsErrorto be set)You can change/set the consent text in the
InitiateRecordercommand, but you cannot omit that trial, because it is when the browser requests access to the recording device: without it it would simply be impossible to record anythingJeremy
-
AuthorPosts