Reply To: mathematical operations with variables

PennController for IBEX Forums Support mathematical operations with variables Reply To: mathematical operations with variables

#4918
Jeremy
Keymaster

Hi Cindy,

I have to admit that the way that Var elements work can be very confusing, and I have struggled a lot about how to best implement them when designing PennController: the current state is my best attempt at making them user-friendly, so I guess it is complicated indeed… (or maybe I’m just very bad at making things simple)

At some point (and you’ve reached that point here) it becomes important to understand how PennController commands are evaluated: the new* commands that create in-trial elements are actually evaluated when you open your experiment (ie. your experiment always starts by referencing all the elements of all your trials), but the commands that you call on the elements (print, wait, …) are evaluated upon runtime. Now, that delayed evaluation is also what makes it possible to use getVar('name') with the expectation that it will return the value at the time when the command is evaluated. However, if you try to use getVar('name') anywhere else, like inside a new* command, it will return something completely different (something that “represents the need to evaluate later,” if you will, but not the value itself)

TL;DR: you cannot refer to getVar inside the parentheses of your newVar commands (or any new* command for the matter). Here is a very minimal change to the bit of code you report that should do the trick (notice the two-step subtraction method):

newVar('a_set').set( getVar('a_items_selected') ).set( v => row.a_maximum_items-v >= 0 ),
newVar('b_set').set( getVar('b_items_selected') ).set( v => row.b_maximum_items-v >= 0 )
     .test.is(true).and( getVar('a_set').test.is(true) )

Here is a full example of a script that uses a slightly different method, hopefully it will give you an idea of how things work (I’m using uppercase and lowecase letters as ‘items’). It’s maybe a bit too sophisticated in some sense, but I think it’s a good illustration of how Var elements work:

PennController.ResetPrefix(null)

AddTable('mytable', `U1,U2,U3,U4,l1,l2,l3,l4,max_U,max_l
A,B,C,D,a,b,c,d,2,3
E,F,G,H,e,f,g,h,3,1`)

// We'll call this function to create three Var, Selector and Text elements for all 8 items in a trial
add_item = (row,item) => [
    newVar(item, false)             // The element is unselected at the beginning of the trial (=false)
    ,
    newSelector(item).callback(     // When the element is clicked, proceed conditionally
        getText('error').remove() 
        , 
        // Note: item[0] below returns the first character of item, which is either U or l
        getVar(item).test.is(false) // Flag as true and increment U/l_selected if was unselected
            .success( getVar(item).set(true) , getVar(item[0]+'_selected').set(v=>v+1) )
            .failure(               // If was selected, unselect, flag as false and decrement U/l_selected
                getSelector(item).unselect()
                ,
                getVar(item).set(false)
                ,
                getVar(item[0]+'_selected').set(v=>v-1)
            )
    )
    ,
    // Don't forget to print text element onto the page and add it to the selector
    newText(row[item]).selector( item )
]


Template( 'mytable' , row=>
    newTrial(
        newText("error", 'You can only select up to '+row.max_U+' uppercase letters and up to '+row.max_l+' lowercase letters!' )
        ,
        defaultText.center().print()
        ,
        // These will keep track of how many items are selected in each category
        newVar('U_selected',0),newVar('l_selected',0)
        ,
        // We use the helper function here to execute the commands defined above on all 8 items
        add_item(row,'U1'),add_item(row,'U2'),add_item(row,'U3'),add_item(row,'U4'),
        add_item(row,'l1'),add_item(row,'l2'),add_item(row,'l3'),add_item(row,'l4')
        ,
        newButton("Validate").center().print().wait(
            // Wait will be validated only if U_selected and l_selected are below or at max_U and max_l
            // NOTE: the forum adds a space between < and = which should NOT be here
            getVar('U_selected').test.is( v => v <= row.max_U )
                .and( getVar('l_selected').test.is( v => v <= row.max_l ) )
                .failure( getText("error").print() )
        )
    )
)

Let me know if you have any questions,

Jeremy