Code component to count mouse clicks

Hello everyone

URL of experiment: *current status pilot study *

Description of the problem:
For my experiment I would like to count mouse clicks on specific stimuli (images) and I defined a number of clicks required on these specific stimuli to go to the next routine. (It is called a concurrent schedule task). Here is the code:

if ((mouse.clicked_name.count("cigaretteIm") >= 5)) {
    continueRoutine = false;
} else {
    if ((mouse.clicked_name.count("mmsIm") >= 5)) {
        continueRoutine = false;
    }
}

where “cigaretteIm” and “mmsIm” are the two stimuli. This code works well in the Builder interface when I run my experiment on my computer. If I try to run it online I get the following error message: TypeError: mouse.clicked_name.count is not a function.

I don’t know if there is an error in my code or if this happens because this function is not implemented.

Could someone help?

Thanks in advance

That is the error. Python has a very convenient .count() function for list objects. JavaScript doesn’t. You will need to find a JavaScript equivalent to this Python language feature.

This just reflects that these are two languages that do things differently: it’s more than just whether they use curly brackets and semicolons. I’m not a Javascript person, so can’t help myself. But this isn’t a PsychoPy-specific question, and is more just a general Javascript programming question, so you can Google much more widely for other people with similar needs, e.g.

Thank you for your answer. I will try to find an equivalent in JavaScript.

Hi @aelreym, I’ve deleted the duplicate post you made. If it is the same issue, please just keep this existing thread going.

Hi ok @Michael , no problem. So if someone could help translating this code in a Javascript code it would be great, as I am not able to do it.

The solution in a broad sense is to define variables at the start of the routine that are counters for the number of times each object has been clicked. Then you need code to update those counters every time one is clicked. Something like this might work.

At the start of the routine:

var cigaretteImCounter = 0;
var mmsImCounter = 0;

each frame:

if (mouse.getPressed(cigaretteIm)){
    cigaretteImCounter = cigaretteImCounter+1;
}
if (mouse.getPressed(mmsIm)){
    mmsImCounter = mmsImCounter + 1;
}

if (cigaretteImCounter >= 5) {
    continueRoutine = false;
} else {
    if (mmsImCounter >= 5) {
        continueRoutine = false;
    }
}

Or something along those lines. The salient pieces are:

  1. You need to manually create the relevant counters
  2. You can check click locations using mouse.getPressed(stimulus)

I have tried this but I got the following error message: * ReferenceError: Can’t find variable: CigaretteImCounter

This is my bad, there’s a scope issue with the way code components work that I only just worked out. Remove “var” from in front of the names of the variables in the start routine code, that should hopefully fix it.

Great, the first part of the code seems to work.
But there is something wrong with the part:

if (cigaretteImCounter >= 5) {
coutinueRoutine = false;
}

Because as soon as I add this part to the code, the Routine starts and ends directly, going immediately to the next routine.

Without you clicking at all?

yes

Ah, this is my mistake as well. I did some more reading and found mouse.getPressed works differently in psychoJS compared to the standard way JS handles mouse clicks, so that won’t work. The normal Python solution of mouse.isPressedIn also won’t work, because that function doesn’t exist in JS. However, I think I may have found a solution:

if (mouse.getPressed()){
    if(cigaretteIm.contains(mouse)){
        cigaretteImCounter = cigaretteImCounter+1;
    } else if (mmsIm.contains(mouse)){
        mmsImCounter = mmsImCounter + 1;
    }
}

if (cigaretteImCounter >= 5) {
    continueRoutine = false;
} else if (mmsImCounter >= 5) {
    continueRoutine = false;
}

Try this JS code for “each frame” instead and see if it works.

I have tried it with this code and still the same: it goes immediately to the next routine, without clicking at all.

I recently built a similar experiment, which continued to the next routine after two clicks. I used an additional variable gotValidClick, which is set to True after the number of clicks you want. The routine will end if gotValidClick is True. The each frame tab would look something like this:

if (mouse.status === PsychoJS.Status.STARTED) {  // only update if started and not finished!
    let buttons = mouse.getPressed();
    const xys = mouse.getPos();
    mouse.x.push(xys[0]);
    mouse.y.push(xys[1]);
    mouse.leftButton.push(buttons[0]);
    mouse.midButton.push(buttons[1]);
    mouse.rightButton.push(buttons[2]);
    
    mouse.time.push(mouse.mouseClock.getTime());
      if (!buttons.every( (e,i,) => (e == prevButtonState[i]) )) { // button state changed?
        prevButtonState = buttons;
        if (buttons.reduce( (e, acc) => (e+acc) ) > 0) { // state changed to a new click
          // check if the mouse was inside our 'clickable' objects
          gotValidClick = false;
            if (cigaretteIm.contains(mouse)) {
              cigaretteImCounter++;
              if (cigaretteImCounter >= 5) 
                  {gotValidClick = true;}
                } else if (mmsIm.contains(mouse)) {
              mmsImCounter++;
              if (mmsImCounter >= 5) 
                  {gotValidClick = true;}
                }
          if (gotValidClick === true) { // abort routine on response
              continueRoutine = false;
            }
        }
      }
    }

Thanks to both of you for helping, I need this for my master’s thesis and I appreciate your help.
@PsyLing Unfortunately this code also doesn’t work, there is no end of routine if I click more than 5 times on the image.

OK. So. I decided to actually have a look at this and have learned some things.

The corsi demo no longer works because the mouse never receives the status “STARTED” (@jon, unintended behavior? see https://github.com/psychopy/psychopy/issues/2844).

There is a way to make your task work. It requires much more code than it probably should, but I have tested this and know it works. Notably, this is direct JS code, we are not going to be going through python. Here’s how to do it.

First, in “begin routine”, place the following:

mouse.x = [];
mouse.y = [];
mouse.leftButton = [];
mouse.midButton = [];
mouse.rightButton = [];
mouse.time=[];
prevButtonState = mouse.getPressed();
mmsImCounter=0;
cigaretteImCounter=0;

Then, in every frame, the following:

let buttons = mouse.getPressed(); // read mouse state
const xys = mouse.getPos(); // get mouse coordinates
mouse.x.push(xys[0]); // add mouse coordinates to x/y list, in principle for data storage, but not implemented right now
mouse.y.push(xys[1]);
mouse.leftButton.push(buttons[0]); // store buttons in button list, likewise for storage
mouse.midButton.push(buttons[1]);
mouse.rightButton.push(buttons[2]);
//debug code
//console.log('x location: ' + String(mouse.x) + ' y location: ' + String(mouse.y));

mouse.time.push(mouse.mouseClock.getTime()); // get mouse time, again for storage that is not implemented
if (!buttons.every( (e,i,) => (e == prevButtonState[i]) )) { // button state changed?
  prevButtonState = buttons; //button state as of last frame, makes sure holding mouse down has not affected anything
  //debug code
  //console.log('new button state detected');
  if (buttons.reduce( (e, acc) => (e+acc) ) > 0) { // state changed to a new click
    // check if the mouse was inside our 'clickable' objects
    gotValidClick = false;
    if (cigaretteIm.contains(mouse)) {
        cigaretteImCounter++;
        if (cigaretteImCounter >= 5) {gotValidClick = true;}
    } else if (mmsIm.contains(mouse)) {
          mmsImCounter++;
          if (mmsImCounter >= 5) {gotValidClick = true;}
    }
    if (gotValidClick === true) { // abort routine on response
        continueRoutine = false;
    }
  }
}

I am virtually certain that will work, but it may not store data from each click at the end of the trial, just the final click (and even that may be kind of iffy). However, I have verified that it will end the trial correctly when you click on one of the things five times.

Additional code will be required to save the complete mouse movement, if that’s data you want.

Thank you Jonathan.
I have just tested it and it still doesn’t work.
Here a screenshot of the mouse properties, if it helps?

This is a guess, but delete the things in the “clickable stimuli” field. With this code you don’t need it, and it may be causing the mouse to behave in a weird way.

EDIT: Also, in my test program, I used polygons instead of images. That may make a difference. You could possibly create polygons that went behind the images if the clickable stimuli trick doesn’t work.

I tried both, it still doesn’t work. Any other idea?
You said that you have verified that it will end the trial after 5 clicks on one of the things, so it works for you?

I made a simple test project that was supposed to emulate yours, and I got it to behave in a way where if I clicked one of two shapes 5x, it would end the trial. Here it is: https://pavlovia.org/jfkominsky/testmousecontains

You can get to the code from there and see for yourself, it is exactly what I copied above.