Jeremy

Forum Replies Created

Viewing 15 posts - 136 through 150 (of 1,522 total)
  • Author
    Posts
  • in reply to: server issue – sources not preloading #10507
    Jeremy
    Keymaster

    Hello,

    You’re using AddHost in place of PreloadZip

    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 side

    Jeremy

    in reply to: Progress bar for each sentence? #10506
    Jeremy
    Keymaster

    Hello 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

    in reply to: Aesthetics of the scale element after selection #10500
    Jeremy
    Keymaster

    Hi Nasim,

    In PennController 2.0, a disabled Scale element adds the disabled attribute to its input DOM elements. So you can specifically target the label DOM elements that are siblings of enabled input DOM elements. Concretely, in your CSS rules, just replace .PennController-Scale .option label:hover { with .PennController-Scale .option input:enabled + label:hover {

    Jeremy

    in reply to: problems with timeout #10499
    Jeremy
    Keymaster

    Hi Noelia,

    All the Timer elements in your trial are started and waited for upon creation: defaultTimer.start().wait()

    This applies to the one named “hurry” too, so newTimer("hurry", 1000).log().start() implicitly stands for newTimer("hurry", 1000).start().wait().log().start()

    Remove .wait() from the default commands, and include it explicitly where you need it

    Jeremy

    in reply to: Audio playing twice #10494
    Jeremy
    Keymaster

    Hi,
    You’re missing the parentheses () on start

    Jeremy

    in reply to: help on ID information #10488
    Jeremy
    Keymaster

    Hello Eleni,

    Place a test inside the Button element’s wait command:

    newTextInput("ID", "").print()
    ,
    newButton("Start")
      .center()
      .print()
      .wait( getTextInput("ID").test.text(/\w/) )

    Jeremy

    in reply to: Audio playing twice #10486
    Jeremy
    Keymaster

    Hi,

    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 second wait 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 the callback command of a Timer element for example:

    newAudio("audio", row.audio)
        .center()
        .print()
        .wait()
    ,
    newTimer(10).callback( getAudio("audio").wait().remove() ).start()

    Jeremy

    in reply to: Penn Controller Audio Segment Playing #10477
    Jeremy
    Keymaster

    Hi,

    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

    in reply to: Presentation issue in an experiment #10476
    Jeremy
    Keymaster

    Hi,

    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

    in reply to: Questionnaire with questions on the same page #10471
    Jeremy
    Keymaster

    Hi 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

    in reply to: Slow loading of images #10469
    Jeremy
    Keymaster

    Hello,

    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

    in reply to: Questionnaire with questions on the same page #10465
    Jeremy
    Keymaster

    Hi 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

    in reply to: Data-collection link doesn’t work #10457
    Jeremy
    Keymaster

    Hi 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 folder

    That 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

    in reply to: newhtml doesn’t work #10454
    Jeremy
    Keymaster

    Hi,

    You can still use items = [ ["completion", "Form", {continueMessage: null, html: { include: "completion.html" }}] ] if you’d like

    When inserted inside a PennController trial via newHtml, the DOM elements are not added immediately, so document.getElementById("demo") will be undefined 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

    in reply to: newhtml doesn’t work #10451
    Jeremy
    Keymaster

    Hello,

    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

Viewing 15 posts - 136 through 150 (of 1,522 total)