Forum Replies Created
-
AuthorPosts
-
Jeremy
KeymasterHello,
You’re using
AddHost
in place ofPreloadZip
You also need to upload a
.htaccess
file to your server in the directory that contains your zip file to grant CORS access. Currently your server does not respond with the right access headers, so something must be misconfigured on your server’s sideJeremy
Jeremy
KeymasterHello Evgeniya,
At the top of your script, define the size you want for that progress bar, for example 100*20px:
const PROGRESS_SIZE = {w: 100, h: 20}
Then in your trial, print the Canvas elements that stand for your progress bar and run a function to update it when you launch the timeout. In this example, I’m setting a duration of 5000m:
newCanvas("progressContainer", PROGRESS_SIZE.w, PROGRESS_SIZE.h) .add(0,0,newCanvas("progress", PROGRESS_SIZE.w, PROGRESS_SIZE.h).color("green")) .css("border","solid 1px black") .center() .print() , newVar("timerInfo").set(()=>[Date.now(),5000]) // set the duration here , newFunction( "updateProgress", ()=>{ const [s,d] = getVar("timerInfo")._element.value; const p = (Date.now()-s) / d; if (p>1) return; getCanvas("progress").size((1-p)*PROGRESS_SIZE.w,PROGRESS_SIZE.h)._runPromises(); window.requestAnimationFrame( getFunction("updateProgress")._element.function ); } ).call()
Here’s a full example:
const PROGRESS_SIZE = {w: 100, h: 20} newTrial( newCanvas("progressContainer", PROGRESS_SIZE.w, PROGRESS_SIZE.h) .add(0,0,newCanvas("progress", PROGRESS_SIZE.w, PROGRESS_SIZE.h).color("green")) .css("border","solid 1px black") .center() .print() , newVar("timerInfo").set(()=>[Date.now(),5000]) // set the duration here , newFunction( "updateProgress", ()=>{ const [s,d] = getVar("timerInfo")._element.value; const p = (Date.now()-s) / d; if (p>1) return; getCanvas("progress").size((1-p)*PROGRESS_SIZE.w,PROGRESS_SIZE.h)._runPromises(); window.requestAnimationFrame( getFunction("updateProgress")._element.function ); } ).call() , newController("AcceptabilityJudgment",{ s:"What did the cat chase that was running to hide?", q:"How good does this sentence sound?", as:['1','2','3','4','5'], presentAsScale: true, timeout:5000 // the durations have to match }) .center().print().wait().remove() , getCanvas("progressContainer").remove(), newButton("Next").center().print().wait() )
Jeremy
Jeremy
KeymasterHi Nasim,
In PennController 2.0, a disabled Scale element adds the
disabled
attribute to itsinput
DOM elements. So you can specifically target thelabel
DOM elements that are siblings of enabledinput
DOM elements. Concretely, in your CSS rules, just replace.PennController-Scale .option label:hover {
with.PennController-Scale .option input:enabled + label:hover {
Jeremy
Jeremy
KeymasterHi Noelia,
All the Timer elements in your trial are
start
ed andwait
ed for upon creation:defaultTimer.start().wait()
This applies to the one named “hurry” too, so
newTimer("hurry", 1000).log().start()
implicitly stands fornewTimer("hurry", 1000).start().wait().log().start()
Remove
.wait()
from the default commands, and include it explicitly where you need itJeremy
Jeremy
KeymasterHi,
You’re missing the parentheses()
onstart
Jeremy
Jeremy
KeymasterHello Eleni,
Place a
test
inside the Button element’swait
command:newTextInput("ID", "").print() , newButton("Start") .center() .print() .wait( getTextInput("ID").test.text(/\w/) )
Jeremy
Jeremy
KeymasterHi,
After the
wait
command on the Audio element, instead of removing it immediately, tell the script to wait a second time before removing it. Now, you don’t want that secondwait
to block the rest of the script (ie. you want the TextInput element to appear immediately after the first playback, even if the audio isn’t played back a second time) so you need to run those instructions in parallel, in thecallback
command of a Timer element for example:newAudio("audio", row.audio) .center() .print() .wait() , newTimer(10).callback( getAudio("audio").wait().remove() ).start()
Jeremy
Jeremy
KeymasterHi,
There is no built-in command in PennController for that, but it’s easily implementable using a Function element:
newTrial( newButton("Start").print().wait().remove() , newAudio("sample", "https://freetestdata.com/wp-content/uploads/2021/09/Free_Test_Data_100KB_MP3.mp3") , newFunction("playSegment", async ()=>{ const a = getAudio("sample")._element.audio; a.pause(); a.currentTime = 1.25; // start at 1.25s a.play(); await new Promise(r=>(function checkAudio() { if (a.currentTime >= 2.30) { // end at 2.30s a.pause(); r(); } else setTimeout(checkAudio); })()); }) , newButton("Play segment").callback( getFunction("playSegment").call() ).print() , newButton().wait() )
Jeremy
Jeremy
KeymasterHi,
Focus automatically goes to the most recently added TextInput element. If you want to give focus back to the first TextInput element and scroll back to the top of the page at some point, you can execute some javascript code using the Function element (before you create and print the Button element for example):
newFunction( ()=>{ window.scrollTo(0,0); document.querySelector(".PennController-TextInput.PennController-alter").focus(); }).call()
Jeremy
Jeremy
KeymasterHi Marta,
My bad, I thought the content of the Text elements would be logged, but that’s not the case. One thing you could do is redefine the
question
function like this:question = (number,text) => newScale("Rating-"+number, "Strongly Disagree", "Disagree", "Neither Agree or Disagree", "Agree", "Strongly Agree") .log() .labelsPosition("top") .before( newText(text).log() ) .size("auto") .print();
The Text elements will be reported in the results file in the order in which they were printed, so that the Text element of the first reported row will correspond to the Scale element named Rating-0, and so on. The content of the Text elements will be found in the PennElementName column of their corresponding rows
Jeremy
Jeremy
KeymasterHello,
There are five files referenced in your tables that are absent from your project’s Resources folder:
- Blue 1 inches copy 4.png
- Blue 2 inches copy 3.png
- Blue 4 inches copy 2.png
- Pink 1 inch copy 4.png
- Pink 2 inches copy 3.png
I only experience issues with the trials depending on these resources; all the other ones run smoothly with no noticeable preloading time
Jeremy
Jeremy
KeymasterHi Marta,
Is the
question
from your code the one defined in this message? And by randomizing “the questions (that is the scale’s labels)”, do you mean that you want to randomize “Strongly Disagree”, “Disagree”, “Neither Agree or Disagree”, “Agree” and “Strongly Agree”? If so, do you want to randomize the labels for every single question (option 1), or do you want to randomize the labels once at the beginning of the trial and use that order for all the questions in the trial (option 2)? Or do you just want to randomize the questions in the array, but keep the labels constant (option 3)?Option 1:
const labels = ["Strongly Disagree", "Disagree", "Neither Agree or Disagree", "Agree", "Strongly Agree"]; question = (number,text) => { fisherYates(labels); return [ newText("Question-"+number, text) , newVar("labels-"+number, labels.join(";")).log() // log the order of the labels for this question , newScale("Rating-"+number, ...labels) .log() .labelsPosition("top") .before( getText("Question-"+number) ) .size("auto") .print() ]; } var test; newTrial("WBSI", ...wbsiQuestions.map( (v,i) => { if (i==0) test = getScale("Rating-"+i).test.selected(); else test = test.and( getScale("Rating-"+i).test.selected() ); return question(i,v); }) , newButton("next", "Next") .print() .wait( test ) )
Option 2:
const labels = ["Strongly Disagree", "Disagree", "Neither Agree or Disagree", "Agree", "Strongly Agree"]; question = (number,text) => [ newText("Question-"+number, text) , newScale("Rating-"+number, ...labels) .log() .labelsPosition("top") .before( getText("Question-"+number) ) .size("auto") .print() ]; var test; newTrial("WBSI", fisherYates(labels) , ...wbsiQuestions.map( (v,i) => { if (i==0) test = getScale("Rating-"+i).test.selected(); else test = test.and( getScale("Rating-"+i).test.selected() ); return question(i,v); }) , newButton("next", "Next") .print() .wait( test ) ) .log( "labels" , labels.join(";") ) // log the order of the labels for this trial
Option 3:
question = (number,text) => [ newText("Question-"+number, text).log() // log to retrieve the number-text associations , newScale("Rating-"+number, "Strongly Disagree", "Disagree", "Neither Agree or Disagree", "Agree", "Strongly Agree") .log() .labelsPosition("top") .before( getText("Question-"+number) ) .size("auto") .print() ]; var test; newTrial("WBSI", fisherYates(wbsiQuestions) , ...wbsiQuestions.map( (v,i) => { if (i==0) test = getScale("Rating-"+i).test.selected(); else test = test.and( getScale("Rating-"+i).test.selected() ); return question(i,v); }) , newButton("next", "Next") .print() .wait( test ) )
Jeremy
Jeremy
KeymasterHi Daoxin,
Discrepancies between the two links often have to do with them running different groups: after taking the demonstration link a few times, you might be running in, say, group 2, but if you’ve never completed the study with the data-collection link before, you’ll be running it in the first group using that link. If there are problematic references in your table(s) for the first group, but not the second, then you’ll only experience issues with the data-collection link (although you can force a group when running your experiment with either link using
server.py?withsquare=N
)
So first thing to do is double-check your file references: make sure each filename from your tables is found across the zip files and the files in your project’s Resources folderThat being said, I couldn’t really replicate your specific issue, because the two high* zip files you preload are several hundreds MBs each, and my browser would fail to complete the background downloads, so some video files (as part of a zip file) simply were never downloaded and therefore could not be preloaded at all, regardless of which group or link I would use. That’s another potential source of discrepancy: maybe the zip files had already been downloaded before during your test runs with the demonstration link and you have them in your browser’s cache, but running the data-collection link might have prompted new download requests that did not complete, as was the case for me. It would be worth investigating whether/how you could make your video files, and so ultimately your zip files, lighter so that they can be properly downloaded in time
Jeremy
Jeremy
KeymasterHi,
You can still use
items = [ ["completion", "Form", {continueMessage: null, html: { include: "completion.html" }}] ]
if you’d likeWhen inserted inside a PennController trial via newHtml, the DOM elements are not added immediately, so
document.getElementById("demo")
will beundefined
by the time the script gets executed. You could delay execution until the reference is defined:(function ud(){try{document.getElementById("demo").innerHTML = functionName();}catch(e){window.requestAnimationFrame(ud)}})();
Jeremy
Jeremy
KeymasterHello,
Do you call
wait
on any element in your trial labeled “final” so as to halt the execution of the script, and prevent it from finishing the trial immediately?Jeremy
-
AuthorPosts