Load + Filter with scripts

I have a project using CMS Filter and CMS Load attributes on the same page. We have a couple of fields on the collection that are text fields with comma separated lists of values that we are using for filtering (for example a list of states that would have a value in that field like CA,NY,MD). We’re using script elements on each item to parse the list into individual hidden html elements, and assigning each of those elements with the fs-cmsfilter-field attribute in order to filter on each option present in a given item.

This works great for CMS filter. The problem then arises when we try to combine this with CMS Load – any elements that aren’t loaded in the first page won’t have the script element executed, and thus won’t have the fields present for filtering.

I’ve thought about using a callback on cmsload for the additems event, and using that to try to iterate through the newly fetched items and execute the appropriate script for each item, but am running into several issues with that approach.

  1. It doesn’t seem like these additems events fire during the initial loading of the CMSList, so that won’t work to execute scripts during initial load, only after the full list is loaded, which for our purposes will be too slow with thousands of cms items to be loaded.
  2. Even if we were able to fire additems on the initial page load, it’s unclear to me exactly how we’d want to add these elements. Would we try to create html elements and let cmsfilter detect them, even though the new items aren’t actually in the dom? Or would we try to edit the cmsitem within cmscore? Or do we need to do something within cmsfilter?

Is there some other way to accomplish this combination of Filter and Load?

1 Like

Hey @ebryan! Instead of additems you can use the renderitems event that will run each time new items are rendered on the page. However, you can avoid having to use a callback if you use CMS Nest with the second option as you already have a comma-separated list on your items (I do however believe you’ll need to add another collection with the states and reference this collection on your existing collection).

Thanks Luis. We’re trying to avoid using more collections because we’re approaching the collections limit on our site. In addition, we have some fields that are slightly more complex (some are full on json strings with multiple fields that we are parsing out, some for filtering and others for displaying); the states example was just one that was the simplest to describe what we’re trying to achieve.

The problem with renderitems is that on initial page load, the elements beyond the first page of items fetched by cmsload are not actually rendered they’re blocked by cmsfilter.

This video might help clarify the conflict https://www.loom.com/share/350a1aa492ae49f7a8fcfdaf57a509a1

Worst case scenario we can do the collections and that should get us through, but we’d very much prefer not to.

1 Like

You can always prevent CMS Filter from filtering the list by using defer fs-attributes-preventload='true' for the filter script and initializing it with the CMS Load callback. If you could provide some links (read-only or live site) we can look at other options

<script>
      window.fsAttributes = window.fsAttributes || [];
      window.fsAttributes.push([
        'cmsload',
        (listInstances) => {
          console.log('cmsload Successfully loaded!');

          // The callback passes a `listInstances` array with all the `CMSList` instances on the page.
          const [listInstance] = listInstances;

          window.fsAttributes.cmsfilter.init();

          // The `renderitems` event runs whenever the list renders items after switching pages.
          listInstance.on('renderitems', (renderedItems) => {
            console.log(renderedItems);
          });
        },
      ]);
    </script>

I have the same problem.

Luis- this is a different client project from the one mentioned in my other post.

In this project we have to dynamically generate the in-collection-list content that the filtering matches against. This works great for the first 100 but fails with CMS Load.

I’ve found two API events mentioned for interacting with the load process-

listInstance.on('renderitems', (renderedItems) => { ... });

Which isn’t useful here because it fires only as the items are rendered into the DOM, which means the in-memory list doesn’t have the filter data needed to work.

listInstance.on('additems', (addedItems) => { ... });

This one looked very promising but we’re only seeing additems fired after ALL the items have loaded, which takes 30 to 60 seconds for a first time visitor.

How can we be notified as each page loads, and modify the in-memory data for each added item set as it arrives?

Ideally, this would include Page 1, so we can skip the kludgy DOM manipulations altogether, and just feed the filtering rules directly into the in-memory list.

Hey @memetican can you share a link?

Hi @Support-Luis -

Demonstration link-
https://test-cms-filter-cms-load.webflow.io/

Readonly link-
https://preview.webflow.com/preview/test-cms-filter-cms-load?utm_medium=preview_link&utm_source=designer&utm_content=test-cms-filter-cms-load&preview=42fe4cd2f4064b651317a67c14e0b756&workflow=preview

hey @memetican! Instead of using the CMS Load callback, try using the CMS Filter one, there the renderitems and additems events fire each time a now page from pagination is added.

The code you already have there to listen to these events would look like this:

<script>
  window.fsAttributes = window.fsAttributes || [];
  window.fsAttributes.push([
    'cmsfilter',
    (filterInstances) => {
      console.log('cmsload Successfully loaded!');

      const [filterInstance] = filterInstances;

      // The `renderitems` event runs whenever the list renders items after switching pages.
      filterInstance.listInstance.on('renderitems', (renderedItems) => {
        console.log('renderitems event', renderedItems);
      });

      filterInstance.listInstance.on('additems', (addedItems) => {
        console.log('additems event', addedItems);
      });

      filterInstance.listInstance.on('switchpage', (targetPage) => {
        console.log('switchpage event', 'The user has navigated to the page number ', targetPage);
      });
    },
  ]);
</script>

Ha! That looks exactly like what I am looking for.
I can see it re-triggers those events even when loading from the cached store.

So then to modify the filter data dynamically from that callback event, do I modify the element itself at that point ( and it’s then mutationobserver-detected and reparsed? ) or do I modify the props of the item directly in the event?

I can see an object hierarchy of the form item.props.ID.values[] where ID is the config-assigned filter field identifier, and is a set of values. This is very promising.

Is there a reference for how to interact with the in-memory filter data?

image

You should be able to modify the element directly and have the filter working correctly.

The in-memory element? Do you mean to modify the content of props.data.elements[] directly?

Just to put it out there, the filter data we’re manufacturing doesn’t have a purpose in the DOM, other than to feed FS filter.

Here’s an example- let’s suppose the CMS describes an event as running from jun 1 to sep 30, but only Mondays. I calculate that last of dates, and it might be hundreds.

Ideally, I’d be able to feed it directly to FS filter and associate it with that CMS item, without needing to manufacture DOM elements and have FS filter then parse that data back out.

Is that possible, or would my filter data be overwritten by a re-parse of the DOM element at some point?

Hey @memetican! Sorry for the late reply, can you try using this code?

const repeat = (cmsItem) => {
  const letter = cmsItem.element.querySelector('[fs-cmsfilter-field="letter"]');
  letter.textContent = letter.textContent.repeat(3);

  cmsItem.collectProps({ fieldKey: 'fs-cmsfilter-field' });
};

window.fsAttributes = window.fsAttributes || [];
window.fsAttributes.push([
  'cmsload',
  (listInstances) => {
    const [listInstance] = listInstances;

    listInstance.items.forEach(repeat);

    listInstance.on('additems', (addedItems) => {
      addedItems.items.forEach(repeat);
    });
  },
]);

Hey Luis, it partly works.
You can try it here-
https://test-cms-filter-cms-load.webflow.io/

And see the code below in the codepen.

A few problems-

  • It seems to update all at once but not as each paginated page is loaded into the db. It’s happening instead only at the end when all the pages have loaded.
  • When it does complete loading, the user’s filter settings are not applied to the updated data, so items are hidden unless they change their settings and change them back.
  • Somehow I’m not seeing the additems event fire at all. Just one main cmsload event that fires after all pages have loaded.