Problem with filtered dataset in a repeater when the page is refreshed

I’m hoping that some kind person could give me some guidance on what I can do to inprove my code which filters a dataset.

My website www.foulshamvillagehall.org is built in Wix Editor. It previously made use of the Wix Events app, but this proved overly complex for the users, so I have replaced it with a simpler CMS/Repeater combination.

The code:

import wixData from "wix-data";

// This function calculates the number of whole days from now until a specified date (targetDate)

function calculateDaysUntil(targetDate) {
    const oneDay = 24 * 60 * 60 * 1000; // one day in milliseconds
    const start = new Date(); // create current date constant (start)
    const end = new Date(targetDate); // create target date constant (end)
    const totalDays = Math.round(Math.abs((end - start) / oneDay)); // Calculate the difference in days (totalDays)
    return totalDays; // return the result
}

// Load page

$w.onReady(function () {

    $w('#eventsDataset').onReady(() => { // load dataset

    // The following code filters the #eventsDataset to return only events which are on or after the current date
    // It then counts the number of results in the filtered dataset and shows or hides the #noEventsText element accordingly
        
        let dataset = $w("#eventsDataset"); // get eventsDataset (dataset)
        const today = new Date(); // get the current date 
        const FutureEvents = wixData.filter().ge("date", today); // define FutureEvents criteria (dates greater or equal to today)

        dataset.setFilter(FutureEvents) // apply the filter criteria to the dataset
            .then(() => {
                return dataset.getTotalCount(); // return the number of results in the filtered dataset
            })
            .then((count) => {
                if (count === 0) { // if there are no results
                    $w("#noEventsText").show(); // show the "#noEventsText" element
                } else {
                    $w("#noEventsText").hide(); // hide the "#noEventsText" element
                }
            })

// The following code loops through each repeater item and creates the text to populate the #eventTimes element (with start and end time)
// It also calculates how many days until the event and populates the #dtgText element with the result

        $w("#eventsRepeater").forEachItem(($item, itemData, index) => {
            let eventDate = new Date(itemData.date) // set eventDate variable as the date field of the currentItem
            let startTime = eventDate.toLocaleTimeString('en-US', { timeStyle: 'short' }).toLowerCase(); // extract start time from eventDate and store in startTime variable

            if (!itemData.endTime) { // if there is no endTime
                var endTime = "" // set endTime variable to null
            } else {
                var endTime = " to " + new Date("01/01/2024 " + itemData.endTime).toLocaleTimeString('en-US', { timeStyle: 'short' }).toLowerCase(); // set endTime to end time of currentItem and cnovert am/pm to lowercase
            }

            $item("#eventTimes").text = startTime + endTime; // set the eventTime element to startTime + endTime
            let daysToGo = calculateDaysUntil(eventDate); // get the number of days until event
            if (daysToGo === 1) {  // set the text accordingly
                var txt = " day"
            } else {
                var txt = " days"
            }
            $item("#dtgText").text = "in " + daysToGo + txt; // set the #dtgText element to number of days plus txt

        });

    });

});

I have added some code to filter the dataset so that only events in the future are displayed in the repeater (ie past events do not show). The code also calculates the number of days until each event and this is also displayed in the repeater.

On the whole, this works well, but today I noticed (on my PC) that if, whilst on the events page, I refresh the browser, it will briefly display a past event. If I do the same on my mobile, it even shows me an “empty” event with default repeater values and this doesn’t go away.

It seems that there is some kind of timing or syncing issue, but not sure how best to resolve it. Any advice on how my code could be improved would be greatly received. I’m a relative novice when it comes to Velo, but eager to learn.

Thank you in advance.
Mike

Hello, rightclicksolutions !!

Personally, I thought it might be better to use onItemReady() instead of forEachItem(). Additionally, a simpler way to solve this problem would be to initially hide the repeater and then show it when the items are loaded. By hiding the repeater initially and writing code to display the repeater inside onItemReady(), you can achieve this behavior. (The onItemReady() callback responds when new items are loaded into the repeater.)

Also, personally, I think that for simple cases like this, it might be clearer to use wix-data to fetch items (and filter them if needed) and then directly assign the retrieved items to the repeater’s .data property. onItemReady() reacts when new data is assigned to .data, so it might be good to place onItemReady() at the beginning of your code. While datasets are very useful, controlling timing can sometimes be challenging, so deliberately choosing not to use them can be a valid approach.


import wixData from "wix-data";

$w.onReady(function () {

    $w("#eventsRepeater").onItemReady(($item, itemData) => {

        let eventDate = new Date(itemData.date);
        let startTime = eventDate.toLocaleTimeString('en-US', { timeStyle: 'short' }).toLowerCase();

        let endTime = itemData.endTime
            ? ` to ${new Date("01/01/2024 " + itemData.endTime).toLocaleTimeString('en-US', { timeStyle: 'short' }).toLowerCase()}`
            : "";

        $item("#eventTimes").text = startTime + endTime;
        let daysToGo = calculateDaysUntil(eventDate);
        let dayText = daysToGo === 1 ? " day" : " days";
        $item("#dtgText").text = `in ${daysToGo}${dayText}`;

        $w("#eventsRepeater").show();

    });

    wixData.query("EventsCollectionName") // change
        .ge("date", new Date())
        .find()
        .then((results) => {

            const items = results.items;

            if (items.length === 0) {
                $w("#noEventsText").show();
                $w("#eventsRepeater").hide();
            } else {
                $w("#noEventsText").hide();
                $w("#eventsRepeater").data = items;
            }
        })
        .catch((error) => {
            console.log(error);
        });

});

function calculateDaysUntil(targetDate) {
    const oneDay = 24 * 60 * 60 * 1000;
    const start = new Date();
    const end = new Date(targetDate);
    const totalDays = Math.round(Math.abs((end - start) / oneDay));
    return totalDays;
}

Thank you for your comprehensive reply onemoretime. That looks to be a much neater solution… I clearly have much to learn still.

I’ve adopted your code, although I have moved the wixData.query section up inside the $w(“#eventsRepeater”).onItemReady function. Not sure if it’s strictly necessary but it seemed to make sense to me.

I’ve tried your suggestion of hiding/showing (and even collapsing/expanding) the repeater but whatever I try, the page still shows the old event(s) briefly when I refresh the browser (both on PC and mobile). Am I worrying unduly or is this just a quirk of how browsers work when refreshed?

Hi, rightclicksolutions !! I think it’s better to keep onItemReady and wixData.query as I suggested, as it makes the timing of code execution clearer. I apologize that the code I suggested didn’t work as expected…

Just to confirm:

  1. Is the repeater set to be hidden by default in the editor?
  2. Have you correctly changed the collection name in wixData.query to match your collection name?
  3. Have you disconnected the repeater from the dataset? (Since this code does not use a dataset, the connection between the collection and the repeater must be removed.)

Please check these points and adjust as needed.

Hi onemoretime,

Thank you for your continued support with this. Your code looks good, so I think I must be missing something else. :roll_eyes:

Yes, I’ve set the repeater to default to hidden and the collection name is correct in the code. I’m a bit confused about disconnecting the repeater from the dataset. I have disconnected the repeater itself, but the individual elements are still connected to their respective dataset fields. If I disconnect these, the repeater doesn’t know which fields to display in which elements so they just display default text. I have therefore reconnected the elements.

Now, here’s the odd thing. On the published site, when I navigate to the events page via the site menu, the repeater displays all events, both past and future. If I hit the browser refresh button, it then only shows the future events. This suggests that the code is not running when navigating to the events page, but it is running when the browser is refreshed. I’ve added a console.log (items) after the query and in both cases it logs just the future event.

I think I may just revert to the original code, but would love to know more about why this is behaving the way that it is.

Thanks,
Mike

What happened when you disconnected all the elements from the dataset and conducted the experiment?

It just displayed the default placeholder text that was in each of the elements (ie the text that you see when in edit view)

When loading items into a repeater, there are generally two main methods: using a dataset or using Velo code. It is possible to implement this with a dataset as well, and it can be advantageous in simpler cases. However, for clarity, I chose to explain using only Velo code this time. Even when using a dataset, there might be cases where some Velo code is needed. By starting with Velo code alone, you can better understand when to use a dataset versus Velo code.

If you decide to load items using only a dataset, all elements must be connected to the dataset, which is the case when you complete everything using only the editor (a method that allows loading data without knowing Velo code). Additionally, even when using a dataset, there may be cases where the dataset serves only as a buffer from the collection, without directly connecting to the elements. In such cases, some Velo code might still be needed to extract data from the dataset and pass it to the repeater.

On the other hand, if you decide to implement everything using only Velo code, there is no need to connect all elements to a dataset (the dataset itself is unnecessary). In this case, it is normal for default values to be displayed in the editor because Velo code has not been executed yet. This means that if you want something to be displayed in the editor as well, you should use a dataset.

I checked your site and found that when you loaded the page (home) and navigated to Events through the navigation menu, future schedules were displayed correctly, so it seems to be working as intended. The reason why dummy data appears before switching to future schedules when you press the refresh button on the /event page might be that the dataset remains connected. It looks like Velo code is overriding the repeater data when the /event page is loaded.

If I am mistaken, I apologize. Also, when confirming the behavior of the current implementation, please use the live site instead of preview mode.

Also, I noticed that there seems to be an error (marked in red) in the Velo code in the screenshot you provided, so I’m concerned about that.

I must thank you again for your generous support… I am learning a lot and hope I’m not imposing too much on your time.

I must apologise… I’m testing the new code in a test site https://rightclicksolutions.wixsite.com/newfrosthalltest rather than my original site. If I click “New Events” on the menu, it initially displays both past and future events. If I then refresh the browser, the past event disappears. What I still don’t understand about this is that I have also logged the query results (items) to the console and that only logs the future event, so I can’t understand why the repeater (which is populated using the very same variable) is not displaying the same results.

When I disconnected my elements from the dataset, I hadn’t appreciated that I needed to then reconnect them using Velo code which I have now done thus:

        const options = {
            weekday: "long",
            year: "numeric",
            month: "long",
            day: "numeric",
        };

        $item("#eventDate").text = itemData.date.toLocaleDateString('en-US', options);
        $item("#eventTitle").text = itemData.title;
        $item("#eventTeaser").text = itemData.teaser;
        $item("#eventDescription").content = itemData.description;
        $item("#media").items = itemData.media;

I hope that I have done this is correctly. It appears to have cured that particular issue.

Yes, there are a couple of underlined variables in the code which have concerned me too, but this piece of code seems to work. The bit that’s underlined is (end - start) in the function that calculates the number of days until the event:

function calculateDaysUntil(targetDate) {
    const oneDay = 24 * 60 * 60 * 1000;
    const start = new Date();
    const end = new Date(targetDate);
    const totalDays = Math.round(Math.abs((end - start) / oneDay));
    return totalDays;
}

Wix is telling me that the variables ‘end’ and ‘start’ must be of type ‘any’, ‘number’, ‘bigint’ or an enum type, although the function does produce the correct result.

Probably, the errors appearing with end and start can be resolved by the following method.

By using the .getTime() method, you can unify date objects into milliseconds for calculations. In general, when comparing date objects, it’s more reliable to convert them to milliseconds using .getTime() before performing the comparison.

const totalDays = Math.round(Math.abs((end.getTime() - start.getTime()) / oneDay));

I checked the new site for testing. Indeed, there are some unusual behaviors… First, could you embed console.log("onItemReady!!!") inside onItemReady and check how many times this log appears in the console? If it appears more than twice, could you see what happens if you completely “delete” the dataset from the editor, rather than just “disconnecting” it from the elements? Also, please try modifying the code as follows.

      $w("#noEventsText").hide();
      $w("#eventsRepeater").data = []; // here!!!
      $w("#eventsRepeater").data = items;

The console.log(“onItemReady!!!”) was appearing twice in the console.

I’ve deleted the dataset and the console.log(“onItemReady!!!”) now appears only once.
More importantly this seems to have cured the problem. Only the future event now appears when navigating to the New Events page.

I also added the

$w("#eventsRepeater").data = [];

but this wasn’t necessary in the end.

Thank you once again for all your help. I really appreciate it and have learned a lot.

Mike

1 Like

Awesome!!! :rofl: :smiling_face_with_tear: :rofl:

It seems that my assessment was correct after all. The issue was indeed caused by onItemReady() being triggered twice, once through the dataset and once through Velo code, which led to the mysterious behavior. The technique of resetting .data to [] can be useful when onItemReady() does not respond as expected, so it’s good to keep that in mind. It was quite challenging to resolve this issue remotely, but I’m glad that my support was helpful! :wink:

It was impressive how you persevered and worked diligently until the end without giving up midway!

onemoretime

1 Like