Exit button

PennController for IBEX Forums Support Exit button

Tagged: ,

This topic contains 14 replies, has 4 voices, and was last updated by Avatar bgardner 5 days, 23 hours ago.

Viewing 15 posts - 1 through 15 (of 15 total)
  • Author
    Posts
  • #4699
    Avatar
    diyu
    Participant

    Hi Jeremy,

    I’m looking to add an exit button into my experiment. Currently, it has 30 trials, but we don’t have any guarantee that the participants will be able to complete all of them by the end of allotted time. Optimally, I’d like to be able to add a button at the bottom of the screen that stays there consistently throughout the course of the experiment that would allow for all text entry results logged up until that point to be saved, while bypassing the rest of the trials and hopefully going straight to a farewell/exit screen. Is this possible, and if so, how would I implement it?

    Thank you!
    Diane

    #4700
    Jeremy
    Jeremy
    Keymaster

    Hi Diane,

    Yes, it’s possible, but clicking that button would be a point of no return for the participant. Here is one way of doing it (some of this code is hard to make sense of if you’re not already familiar with the arcane of Ibex)

    PennController.ResetPrefix(null);
    
    PennController.AddTable("myTable", `Label,Text
    target,Hello
    filler,hello
    target,World
    filler,world`)
    
    PennController.Sequence("welcome", rshuffle("target","filler"))
    
    PennController.Header(
        newCanvas("confirmation", '75vw', '80vh')
            .settings.css("background-color", "floralwhite")
            .settings.add( "center at 50%", "middle at 25%" , newText("Are you sure you want to send your incomplete results now?") )
            .settings.add( "center at 50%", "middle at 75%" , newScale("quit", "Yes", "No").settings.button() )
        ,
        newButton("stop", "Stop").settings.callback(
            getCanvas("confirmation")
                .print("center at 50vw", "middle at 50vh")
            ,
            getScale("quit").wait()
            ,
            getCanvas("confirmation").remove()
            ,
            getScale("quit").test.selected("Yes")
                .success(
                    getButton("stop").remove()
                    ,
                    newFunction( () => {
                        let mainNode = $("p.PennController-PennController");
                        mainNode.empty();
                        let options = {
                            _finishedCallback: r=>console.log("sent",r) ,
                            _cssPrefix: '',
                            _utils: { setTimeout: ()=>null }
                        };
                        addSafeBindMethodPair('__SendResults__');
                        mainNode['__SendResults__'](options);
                    }).call()
                )
        )
        .print("bottom at 98vh","right at 98vw")
    )
    
    PennController( "welcome" , newButton("Start").print().wait() )
    .noHeader()
    
    PennController.Template( "myTable",
        row => PennController( newButton(row.Text).print().wait() )
    )

    I’ll try to implement a user-friendly option in the next release

    I’m also working on a new PCIbex Farm which will use a new engine, and I’m trying to see if it could try to regular send partial results after each trial

    Let me know if you have any questions

    Jeremy

    #4729
    Jeremy
    Jeremy
    Keymaster

    To follow-up on this, I just released PennController 1.7 which lets you use SendResults() as a command to… send the results. So one can replace the whole newFunction... bit in the code above (including call()) with SendResults()

    #4876
    Avatar
    diyu
    Participant

    Hi Jeremy,

    Apologies for the late reply, and thank you for the code! It’s working as intended!

    #5681
    Avatar
    Ncomeau
    Participant

    Jeremy,

    I am using the method above to allow for exiting and sending results early. I am having some trouble getting the elements associated with my main trials to stop appearing over top of the canvas, and I also can’t seem to pause the timer that I use for a timeout of the main trials (so currently the next trial appears despite having clicked the quit button). Also, just a small thing with the method above: newScale(“quit”, “Yes”, “No”).button() does not seem to add a space between the Yes and No options when they appear on the canvas.

    #5684
    Jeremy
    Jeremy
    Keymaster

    Hi Nickolas,

    Here is an updated version of the code above that should work with PennController 1.8:

    PennController.ResetPrefix(null) 
    
    AddTable("myTable", `Label,Text
    target,Hello
    filler,hello
    target,World
    filler,world`)
    
    Sequence("welcome", rshuffle("target","filler"))
    
    Header(
        newCanvas("confirmation", '75vw', '80vh')
            .css({'background-color': "floralwhite", 'z-order': 9999})
            .add( "center at 50%", "middle at 25%" , newText("Are you sure you want to send your incomplete results now?") )
            .add( "center at 50%", "middle at 75%" , newScale("quit", "Yes", "No").size("10em","auto").button() )
        ,
        newButton("stop", "Stop").callback(
            getCanvas("confirmation")
                .print("center at 50vw", "middle at 50vh")
            ,
            getScale("quit").wait()
            ,
            getCanvas("confirmation").remove()
            ,
            getScale("quit").test.selected("Yes")
                .success(
                    clear()
                    ,
                    SendResults()
                )
        )
        .print("right at 98vw","bottom at 98vh")
    )
    
    newTrial( "welcome" , newButton("Start").print().wait() ).noHeader()
    
    Template( "myTable",
        row => newTrial( newButton(row.Text).print().wait() )
    )

    Let me know if this solved your timer issue too

    Jeremy

    #5693
    Avatar
    Ncomeau
    Participant

    Jeremy,

    Using the clear removes everything so long as all of the elements have been loaded in for the next trial. I have my two choice buttons show up first, then one pops up for replaying the audio. If I use the stop button prior to the replay showing up, it will still appear. Also, the clear does not seem to handle the timer issue as the next trial still appears. When I try to do something like getTimer(“timewindow”).stop() just before SendResults(), it says “Attempted to get an element of an invalid type (Timer);timewindow” in my error log even if I’ve waited long enough for it to start. Perhaps this is due to having added the code to the header? I will try setting a variable “quit” to true and then testing to see if that’s the case after my timeout timer is defined in the main/catch trials.

    #5698
    Jeremy
    Jeremy
    Keymaster

    Hi Nickolas,

    I think you’ve identified the problem: referring to a Timer that is created in a trial from within the header does not work. But I think the opposite should work, namely referring to a Timer that is created in the header from within a trial. So you could try creating (but not starting) your Timer in the header, and manipulating it in your trial. This way you can use stop on it before SendResults in the header.

    Jeremy

    #5755
    Avatar
    Ncomeau
    Participant

    Jeremy,

    Is there a way to pause (rather than stopping) a timer element? I am trying to do this currently by stopping the timeout timer I have for each trial (created in the header) when the user clicks the quit button, and start it back up if they select no. This works as intended if they click quit prior to the timer.wait() call at the end of the main and catch trials, but if they click after it just goes onto the next trial due to the wait being resolved. The other potential solution would be to reset the timer to a significantly higher value while they are on the yes/no quitting canvas, but I’m not sure if that’s possible either. I’m using calls to end(), .stop(), and clear() to handle when they say yes to quitting, so having a way to pause would make things less hacky in that I wouldn’t need to end the trial first.

    Nickolas

    #5756
    Jeremy
    Jeremy
    Keymaster

    Hey Nickolas,

    I haven’t gotten around to implementing it yet, but I will add this to the next release. In the meantime, here’s a hack:

    _AddStandardCommands( function(PennEngine) {
        this.actions = {
            pause: function (resolve){
                if (this.type=="Timer" && this.running) {
                    this.running = false;
                    this.pausedTimestamp = Date.now();
                    this.events.push(["Pause","Pause",this.pausedTimestamp,"NULL"]);
                }
                resolve();
            }
            ,
            resume: function(resolve){
                if (this.type=="Timer" && !this.running && this.pausedTimestamp) {
                    this.resumedTimestamp = Date.now();
                    const offset = this.resumedTimestamp-this.pausedTimestamp;
                    const newStartTime = this.startTime + offset;
                    this.events.push(["Resume","Resume",this.resumedTimestamp,"NULL"]);
                    this.start();
                    this.startTime = newStartTime;
                }
                resolve();
            }
        }
    })

    This technically adds the pause and resume commands to all PennController elements (that is, those that don’t already have them) but will take effect only with Timer elements. I haven’t tested that things get logged properly though, but be my guest.

    Jeremy

    #5845
    Avatar
    Ncomeau
    Participant

    Hi again Jeremy,

    I have successfully been using code similar to that detailed in #5684 to allow the participant to quit via a button that’s present in any given trial. I’ve since added a section to the success of the getScale(“quit”) that allows the participant to provide feedback via a newTextInput element, just before a Send_Results() function call. The logging seems to work fine with the same code at the end of my experiment (I have a separate trial for feedback before sending results), but the same code in the header does not seem to log correctly. This makes me think that properly logging an item must be tied to the header/trial actually completing in full, as currently if they quit the last bit of code in my header just waits indefinitely on a text telling them to return to the Prolific window they came from. Do you have any idea how I might prompt an addition of their feedback to the results file mid header?

    Thanks,

    Nickolas

    • This reply was modified 9 months, 3 weeks ago by Avatar Ncomeau.
    #5848
    Jeremy
    Jeremy
    Keymaster

    Hi Nickolas,

    When used as an in-trial command, SendResults will save the results generated by the trials that have been completed by the time the command is executed. If your TextInput element is (created and) logged immediately before an in-trial SendResults command you shouldn’t expect it to appear in your results file.

    As an alternative solution, you could try to implement the method described in this message on a separate thread. The idea is to insert a trial that can save the results after every other trial in your experiment, but which will only actually do so if you flag a global Var element. That way you can technically complete the current trial (even if doing so by quitting) before SendResults is executed.

    Jeremy

    #6918
    Avatar
    bgardner
    Participant

    Hi,

    I’m trying to modify this to have a button that continues the experiment if they say yes to the consent form and stops the experiment if they say no. (Our online consent form is supposed to have the no option, not just assume they will just exit).

    I have an attempt, but it doesn’t run. I think what I’m confused about is how to end the whole experiment, not just the trial.

    newTrial("Consent",
        newHtml("Consent", "consent.html")
            .center()
            .print()
        ,
        newCanvas("consent_answers", '75vw', '80vh')
            .add("center at 50%", "middle at 25%", 
                newText("I have read this informed consent document and the material contained \
                in it has been explained to me verbally. All my questions have been answered, \
                and I freely and voluntarily choose to participate."))
            .add("center at 50%", "middle at 75%", 
                newScale("agree", "I want to participate in this study.", 
                "I do not wish to participate in this study.")
                .size("10em","auto")
                .button())
        ,
        newButton("stop", "Stop").callback(
            getCanvas("consent_answers")
                .print("center at 50vw", "middle at 50vh")
            ,
            getScale("agree").wait()
            ,
            getCanvas("consent_answers").remove()
            ,
            getScale("agree").test.selected("I do not wish to participate in this study")
                .success(
                    clear()
                    ,
                    SendResults()
                )
            //exit the experiment if they say no, continue if they say yes
        )
        .print("right at 98vw","bottom at 98vh")
    ) 
    
    #6920
    Jeremy
    Jeremy
    Keymaster

    Hi,

    It does not look to me like you want to implement the same kind of “exit” button: what Diane and Nickolas wanted was a button that was visible on every single trial of the experiment, which would let participants end the experiment prematurely whenever they like

    Unless I am misunderstanding what you are trying to do, it looks like you simply want to give that option once to your participants, namely on the consent form page. This is a standard case of linear execution, you could do this for example:

    newTrial("Consent",
        newHtml("Consent", "consent.html")
            .center()
            .print()
        ,
        newText("I have read this informed consent document and the material contained \
                in it has been explained to me verbally. All my questions have been answered, \
                and I freely and voluntarily choose to participate.")
            .print()
        ,
        newScale("agree", "I want to participate in this study.", 
                "I do not wish to participate in this study.")
            .size("10em","auto")
            .button()
            .print()
            .wait()
            .test.selected("I do not wish to participate in this study.")
            .success(
                clear()
                ,
                SendResults()
                ,
                newButton().wait()
            )
    )

    Note that the string in test.selected was missing the final period (the strings must correspond exactly) and I added newButton().wait() to prevent the experiment from moving to the next trial anyway after executing SendResults

    Jeremy

    #6923
    Avatar
    bgardner
    Participant

    Oh, what I was missing is that adding that last button prevents the experiment from moving on. Thank you!

Viewing 15 posts - 1 through 15 (of 15 total)

You must be logged in to reply to this topic.