[SOLVED] Why the onClick action of repeated button items is performed several times after having refreshed the data ?

Hi everyone.

I’m trying to solve an issue that I’m facing since several days now.

I have 2 major elements on a webpage :
- A repeater (repeater1) where each item is refering to a learning course.
- A dropdown box to filter the courses by category

In the repeated items, i have a « show more/less » button that hide or display some text description. But when i click on this button after having change the category (so after having refreshed the data of the repeater), the actions in the button_click event handler are executed several
times instead of being executed one time.

Here is my code:

const maxChar = 190;

$w.onReady(function () {

  $w("#repeater1").collapse();

  $w("#repeater1").onItemReady( ($item, itemData, index) => {
    $item("#image1").src = itemData.image;
    $item("#text17").text = itemData.title;

    let fullTextDescription = itemData.description;

    if ( fullTextDescription.length > maxChar){
      let shortText = fullTextDescription.substr(0, maxChar) + "...";
      $item("#text15").text = shortText;
    } else {
      $item("#button3").collapse();
    }

    $item("#button3").onClick( (event) => {
      let fullText = itemData.description;
      let shortText = fullText.substr(0, maxChar) + "...";

      console.log("Button 3 label is : " + $item("#button3").label);
 
      if ( ($item("#button3").label) === "Show more..." ){
       console.log("Show more clicked, so short text changed to full text and button label changed to show less");
       $item("#text15").text = fullText;
       $item("#button3").label = "Show less...";
      } else {
       console.log("Show less clicked, so full text changed in short text and button label changed to show more");
       $item("#text15").text = shortText;
       $item("#button3").label = "Show more...";
      }
      
      console.log("Button 3 label is now : " + $item("#button3").label);

    } );

    wixData.query("eModules")
    .ascending("index")
    .find()
    .then( (results) => {
       if( results.items.length > 0) {
        $w("#repeater1").data = results.items;
        $w("#repeater1").expand();
       } else {
        $w("#repeater1").collapse();
       }

  } );
});

export function dropdown1_change(event) {
 //Add your code for this event here: 
 let $selectedCategory = "";

  wixData.query("Categories")
  .eq("title", $w('#dropdown1').value)
  .find()
  .then( (results) => {
    let $firstItem = results.items[0];
    $selectedCategory = $firstItem._id;

    wixData.query("eModules")
    .ascending("index")
    .eq("categorie", $selectedCategory)
    .find()
    .then( (results2) => {
      $w("#repeater1").data = results2.items;
    } )
    .catch( (err) => {
      let errorMsg = err;
    } );
  } );
}

Ans this is how it looks lile when clicking on show more after having changed the category (so repeater data refresh) several times :

Nothing happened when having clicked on “Show more” and the console log shows twice the action like if the process would be automatically executed twice ! Can’t figured out why

I tried to connect both elements (repeater and dropdown listbox) to a dataset and also without a dataset (via code). I have the same situation in both configurations.

Based on my code, can anyone help me on this please ?

After further investigations, it appears that each time the data of the repeater is refreshed (after a change of value in the dropdown listbox), it amplifies the problem !

That means, if I change once the category via the change event handler of dropdown1, the first data refresh has no impact on the number of actions made by button3 (has seen in my previous post). If i change a second time the category, the process performed by button3 is executed twice (as seen in the previous post). If i change a third time the category in dropdown1, the process performed by button3 is executed three times. If I change the selection four times, the process performed by button 3 is executed 4 times …

It’s like if each time I do a " $w ( “#repeater1” ). data = results2 . items ;" in the dropdown1_change function, another instance of “$item ( “#button3” ). onClick ( ( event ) => { …” is added to the " $w ( “#repeater1” ). onItemReady ( ( $item , itemData , index ) => {" function for each element. So that when I click on the button, several instances of the onclick action would be performed …

Please I REALLY need some help on that as I’m stuck since several days now

This post is too long. Can’t you make a short summary (and only post the part of code that’s relevant)?

  • tell us what happens if before you reassign some new data to the repeater you do:
$w("#repeater1").data = [];
$w("#repeater1").data = newData;

This might help https://support.wix.com/en/article/corvid-best-practices-for-building-a-corvid-website#avoid-code-that-could-result-in-repeater-performance-limitations

@jonatandor35 Thanks J.D for your reply and sorry if the post was too long. I wanted to be the most accurate as possible.

Let’s then try to make a short summary:

I have a “Show more/less” button contained in each item of a repeater that shows the full or part description of each item.

I also have a dropdown listbox to filter categories of item and which refresh the data of the repeater.

Problem: Each time I change the value of the dropdown list, the data of the repeater is well refreshed but when I click on the “Show more/less” button, the action performed by the button_click is executed as many times as I have selected a different value in the dropdown list (so as many times as the data of the repeater has been refreshed).

Is that clear enough ?

The relevant code is:

$w.onReady(function () {

  $w("#repeater1").onItemReady( ($item, itemData, index) => {
    $item("#image1").src = itemData.image;
    $item("#text17").text = itemData.title;

    let fullTextDescription = itemData.description;

    if ( fullTextDescription.length > maxChar){
      let shortText = fullTextDescription.substr(0, maxChar) + "...";
      $item("#text15").text = shortText;
    } else {
      $item("#button3").collapse();
    }

    $item("#button3").onClick( (event) => {
      let fullText = itemData.description;
      let shortText = fullText.substr(0, maxChar) + "...";
 
      if ( ($item("#button3").label) === "Show more..." ){
       $item("#text15").text = fullText;
       $item("#button3").label = "Show less...";
      } else {
       $item("#text15").text = shortText;
       $item("#button3").label = "Show more...";
      }
    } );

    wixData.query("eModules")
    .ascending("index")
    .find()
    .then( (results) => {
       if( results.items.length > 0) {
        $w("#repeater1").data = results.items;
       } 
  } );
});

export function dropdown1_change(event) {
 //Add your code for this event here: 
 let $selectedCategory = "";

  wixData.query("Categories")
  .eq("title", $w('#dropdown1').value)
  .find()
  .then( (results) => {
    let $firstItem = results.items[0];
    $selectedCategory = $firstItem._id;

    wixData.query("eModules")
    .ascending("index")
    .eq("categorie", $selectedCategory)
    .find()
    .then( (results2) => {
      $w("#repeater1").data = results2.items;
    } )
    .catch( (err) => {
      let errorMsg = err;
    } );
  } );
}

@rod_didier Read the link that Jeff’s posted. It addresses this issue.

@jonatandor35 as you suggest I tried :

$w("#repeater1").data = [];
$w("#repeater1").data = results2.items;

but still get the same problem

@rod_didier from the link that Jeff’s posted:
" Although it is convenient to do so using the scoped $item selector, this practice may cause several event handlers to be set for the same item"
So try the alternative ways.

Thank you Jeff. This article seems to deal with my issue. What I understand is that it is preferable not to use the onItemReady() function but the scoped $item selector. But I would need more info. Does that mean, that I should take the button3_click event out of the onItemReady() function and use a “forItem…” statement instead ?

Initialy that’s what I did but I can try again

@rod_didier Read this article:

Hi Rod,

To be honest, I haven’t looked at this in a while.

But we say here you shouldn’t use $item. " Although it is convenient to do so using the scoped $item selector, this practice may cause several event handlers to be set for the same item , as well as add multiple copies of the callback function to the event handler, affecting the performance of your site."

You want to use one, or a combination of the other APIs listed.

EDIT: Look at J.D’s blog post that he shared. It will guide you.

@jonatandor35 Thanks - I was looking for that and couldn’t find it. Rod - look at that blog post and it will guide you.

@jonatandor35 @jeff, many thanks both of you ! Thanks to this article, I could definitively solve my issue using the event.context method ! Now it works perfectly.

my code is now as follow:

export function button3_click(event) {

 let $item = $w.at(event.context);
 // get the ID of the repeated item which fired an event
 const itemId = event.context.itemId;
 // get all repeater's data, it's stored as an array of objects
 const data = $w("#repeater1").data;
 // use the array methods to find the current itemData and index
 const itemData = data.find((item) => item._id === itemId);
 const index = data.findIndex((item) => item._id === itemId);
 
 let fullText = itemData.description;
 let shortText = fullText.substr(0, maxChar) + "...";
 
 if ( ($item("#button3").label) === "Show more..." ){
        $item("#text15").text = fullText;
        $item("#button3").label = "Show less...";
      } else {
        $item("#text15").text = shortText;
        $item("#button3").label = "Show more...";
      }

}

@rod_didier glad it worked. If this is your entire code you can get rid of the following line:

const index = data.findIndex((item) => item._id === itemId);

since you don’t use it anywhere.

Yes, that’s right. I’ve added it in case i need to do a ForItem() using the itemIndex but here I don’t need it. Thanks again, you Rocks :wink: