Exit button

PennController for IBEX Forums Support Exit button

This topic contains 9 replies, has 3 voices, and was last updated by Jeremy Jeremy 1 week, 1 day ago.

Viewing 10 posts - 1 through 10 (of 10 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

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

You must be logged in to reply to this topic.