Hide Repeater contents based on Data?

I have a Repeater with a button in it, and the button label and URL are connected to a data set.

I want to hide the button if the URL is missing from a row of the data.

I tried doing this in the onItemReady callback of the repeater, checking if the item’s button has an empty URL but it doesn’t doesn’t work … and I read in the documentation that this callback happens before the dataset is applied, so the button’s URL is always empty.

I tried doing it in the onReady callback of the data by iterating through the repeater like this:

$w("#myData").onReady(() => {
	$w("#myRepeater").forEachItem(($w, itemData, index) => {
		if(itemData.url=="") {
			$w("#myRepeaterButton").hide();
		}
	})
})

This also doesn’t work.

I’m not sure if I’m using itemData correctly. If my data has a column “url” can I check itemdata.url for the contents of that field in this row?

I’m also not sure if I can use the global reference to the button in the repeater this way (above), inside the item iterator, as a reference to the specific instance of the button, that is in the row of the Repeater, corresponding to this row of the data. Actually that makes no sense. I’m iterating the data, not the repeater. So I’m a bit lost as to how to find the button in the row of the repeater that corresponds to a row of the data.

Thank you for any tips

1 Like

Hey,

I recreated your example and onItemReady worked for me. Here’s my code:

$w.onReady(() => {
  $w('#myRepeater').onItemReady(($wInRepeater, itemData) => {
    const thereIsNoUrl = !itemData.url
    if (thereIsNoUrl) $wInRepeater('#myRepeaterButton').collapse()
  })
});

I used collapse instead of hide. The difference is that hide keeps the space taken by the button. This is probably not what you want to achieve.

Did you try adding a console.log(itemData) to onItemReady?

When you add a new row, it has no keys (except system ones like _id) defined. It’s like creating an empty object in pure JavaScript. In this case your check won’t work:

itemData.url == "" // false because undefined is not equal empty string

I’m also not sure if I can use the global reference to the button in the repeater this way (above), inside the item iterator, as a reference to the specific instance of the button, that is in the row of the Repeater, corresponding to this row of the data.
You redefine $w in the scope so that it doesn’t refer to the global $w:

($w, itemData, index) => {
  if(itemData.url=="") {
    $w("#myRepeaterButton").hide();
  }
}

In my example, I gave it a different name to make this distinction clearer.

Check out my example and see if it works for you.

Thank you for responding Yevhen.

Your example works, and you’ve made some good suggestions also

  • collapse instead of hide. This didn’t matter in my desktop layout but in mobile it looks nicer.

  • Console Logging helped me find typos. I’m used to other languages where my stupid mistakes would be fatal. Javascript is so forgiving … the extra logging helps a lot.
    But there is still some funky timing going on here. If I call collapse() or hide() it works but if I apply a special label to the button it does not work. You will never see the text in this example:

$w("#repeater1").onItemReady(($wRepeater, itemData, itemIndex) => {
        if (!itemData.url) {
            // $wRepeater("#button6").collapse();      
            $wRepeater("#button6").label = "you will never see this";
        }
    })

I added some logging and I see that in the callback, the itemData is correct for this instance of the repeater but it has not YET been applied to the button. The button data is the static data from the repeater template in the Wix Editor. So after I apply a label here, it gets overwritten by the label from the data. However the collapse() (here commented out) works. I guess because the mapping of the data to the button does not reverse that.

Your example, I propose, happens to work but we are relying here on some very tenuous and poorly documented timing interactions between the callbacks of different objects! I’m happy for now … but not very happy and I bet not forever!

But there is still some funky timing going on here. If I call collapse() or hide() it works but if I apply a special label to the button it does not work.
I agree it looks wired that the values from the dataset aren’t assigned at this stage. But this is by design. See the note after onItemReady description https://www.wix.com/code/reference/$w.Repeater.html#onItemReady:

When using a dataset to populate the contents of your repeated items, the onItemReady() callback function is triggered before the dataset populates the values of your page elements. Therefore, element values that you set using onItemReady()may be overriden when the dataset is ready. To change the values set by the dataset, use forEachItem inside the datset’s onReady() . For more information, see the forEachItem examples.
The following code works:

$w.onReady(() => {
  $w('#dataset').onReady(() => {
    $w('#repeater').forEachItem(($wInRepeaterItem, itemData) => {
      $wInRepeaterItem('#repeatedButton').label = 'Test'
    })
  })
})

Thank you that works. I still think it is a little obscure to have this documented there. If you populate a repeater with a dataset, the repeater is complete when the dataset is “ready”, and that is documented in the iterator function of the repeater. I hope you agree that is obscure. And still not explicit enough to make me confident, even though it works. Anyway, thank you.

I am curious — is there a reason or best practice you are following by using “$” in the localized selector function name? IE why is it $w rather than “wixPage” and why do you use $wInRepeaterItem rather than, say, (itemSelector, itemData) which would be easier to read IMHO ?

Thanks very much

You’re welcome!

Although it does seem obscure, it makes sense if you look at it from a different standpoint. Consider the following example that makes no use of a dataset:

import wixData from "wix-data";

$w.onReady(async () => {
  const buttonRepeater = $w("#buttonRepeater");
  buttonRepeater.collapse();

  buttonRepeater.onItemReady(($w, { url, title }) => {
    const button = $w("#button");
    button.label = title;
    button.link = url;
  });

  const { items } = await wixData.query("Things").find();
  buttonRepeater.data = items.filter(({ url }) => url);
  buttonRepeater.expand();
});

Everything will work as expected because there’s no dataset that overrides the values.

Datasets exist for codeless experience because the majority of our users are not comfortable with code.

As you can see, a repeater can be assigned an arbitrary data.

Regarding the ready state of a dataset. As I understand, a dataset is considered ready when:

  1. The data is loaded.

  2. All components connected to the dataset are populated with the data.

So when the data is loaded, the repeater is assigned new data and its onItemReady callbacks get called.

After that, the step 2 happens.

Regarding $w. I’m not the one who invented the name. I assume it was a marketing decision. And there’s probably a similarity with jQuery.

I don’t think itemSelector is a good name. Normally selector is something like #id. For comparison, there are document.querySelector and document.querySelectorAll in the native JS DOM. So you query a selector to get the element(s).

I used a different name $wInRepeaterItem just to make it easier to see that it isn’t the global $w.

I hope this helps :slight_smile:

This helps a lot. Understanding how and why things MIGHT be used, not just the one way I am using them, is key. Perhaps dataSet should have been called dataConnector. I actually found it annoying at first, I thought, “my database is my data set, why do I need another object to represent it” … now I understand its purpose. Thanks.

By the way you guys have an incredibly powerful tool, I’m really impressed at how easy it is to get a smart, beautiful, site published. And I appreciate your thoughtful support.

Thanks! I’m glad to be of help.

Thank you both for these contributions! This helps a lot.

This no longer seems to work with the new corvid update, Is that possible?
It worked when I first tried it now its now working.

$w(‘#repeater1’).onItemReady(($wInRepeater, itemData) => {
const thereIsNodayAccommodation = !itemData.dayAccommodation
if (thereIsNodayAccommodation) $wInRepeater(‘#accomtext’).collapse()
})

Hi @ruckshani ,

I’ve recreated your example and it worked for me.

Could you share a link to the page with the code so that I’d check it in the context?

@yevhen-pavliuk Thanks so much your reply.

The code is placed on this page https://www.infinittravel.com/Tours/ROMANTIC-BEACH-PARADISE

Also it seems to be working in the preview but not on the live site .

@ruckshani ,

It works as expected for me:


Try it out on another device and see if it recreates for you.

@yevhen-pavliuk Ah yes, thanks so much!

Its working on another device, I had to manually clear my cache to see the functioning site.

But how is it this possible?

The only change I did today was that I enabled caching on the home page, (not on this page)
Furthermore what I was seeing was the default content on the repeater (assume it should show content from previous load and not the default content).

I actually thought it was an issue with the recent corvid changes, though I only saw this happening today. It has happened on the home page destination map too!

@ruckshani ,

You’re welcome! I’m glad the issue’s been resolved.

It’s hard to say what caused the problem without being able to recreate it.

I guess it may be related to caching. Maybe you got a cached version with an issue that was fixed later.

@yevhen-pavliuk Thanks strange though, I need to keep clearing the cache to see the correct data.

Anyway looks like there are others commenting with similar issues on their live site where hidden elements are showing, (I’m having this one on the home page)
https://www.wix.com/corvid/forum/main/comment/5cbd98bc5c3be90081fbec60

Hi @yevhen-pavliuk
Thank you for your contribution and help. I have something similar to what jharuni was having issues with and wondering if you could help me?

I am building a search bar and it is functional however doesn’t come without its errors.
The search bar consists of:

  • database (holds all the listings)
  • repeater (displays all the listings that it’s connected to)
  • javascript code (filters through database using #gamename variable, on inputs command)
  • input (tells code what is searched for in #gamename group)

It’s pretty simple, the database contains the text that I would want pulled out when searched for, the repeater is connected to that database and by default already displays all the listings without the searchbars’ permission. The input specifies what item is searched for in the database category #gamename, and the code I believe filters through which makes the repeater only display what’s searched for.

As mentioned earlier, the problem is that the repeater first displays all the database items even if it wasn’t searched for. How can I fix this? I was considering hiding the repeater on load, and enabling it when something was searched for with a value greater than 0. Or perhaps the repeater could wait to appear until something has been entered in the search bar (best option). Could this work? If so how and can you help me create this code? Being that I am a web designer, I have little to none experience in javascript.
Thank you.

Here is my current code:
import wixData from ‘wix-data’ ;

export function searchbar_keyPress(event, $w) {
let searchValue = $w(“#searchbar”).value;
$w(“#gamedataset”).setFilter(wixData.filter().contains(‘gamename’, searchValue));
}

Hey @danyminko ,

In your case you can do the following:

  1. Collapse (not hide) the repeater on load so that it’s completely hidden and doesn’t take up any space.
  2. Remove the event handler for a key press on the search input—let’s start with a simpler approach.
  3. Add a search button and add a click handler for it (notice async between export and function ):
import wixData from 'wix-data';

export async function searchButton_click(event) {
  const searchInput = $w('#searchbar');
  const searchButton = $w('#searchButton');
  const searchResultRepeater = $w('#searchResultRepeater');
  const searchResultDataset = $w('#gamedataset');

  try {
    searchInput.disable();
    searchButton.disable();

    searchResultRepeater.collapse();
    await searchResultDataset.setFilter(
      wixData.filter().contains('gamename', searchInput.value)
    );
    searchResultRepeater.expand();
  } catch (error) {
    console.error(error);
  } finally {
    searchInput.enable();
    searchButton.enable();
  }
}

Search can be implemented in a variety of ways with different set of features. Check out this video tutorial. You can also consider hiring an expert to help you build exactly what you want.

Hi @yevhen-pavliuk
Thank you for your reply!

I implemented your code into my website, however had no luck.
The repeater still shows, and in addition to this, it doesn’t seem to be filtering anything out. (Search results don’t appear depending on input). I went in preview mode, and when I press on the button, I don’t see any actions in the site code.

Have you tested the code? If so could you share the page with me so I can test it for myself? Maybe we have different things in mind, or I did something wrong.

Warm regards, Dany.