How to Nest + Combine with v2?

Description

I have a Service Items Template with 2 collection lists (photos & videos) - this gets nested to the Service Template. I can see the combine is firing on the Service Items Template (photos are being nested into videos collection successfully here), but when its nested on the Service Template the combining doesn’t come through.

Service Item Template => Resource Integrated Ltd

Service Template => Content Strategy Services | Resource Integrated

I also have some custom code that hooks into the attributes API but I’m not sure if thats interfering with anything:


window.FinsweetAttributes = window.FinsweetAttributes || [];

window.FinsweetAttributes.push([
  'list',
  (listInstances) => {

    listInstances.forEach((instance) => {

      instance.addHook('afterRender', () => {

        // Reinit GLightbox
        const lightboxLinks = document.querySelectorAll(".glightbox");
        if (lightboxLinks.length) {
          if (window.glightboxInstance) {
            window.glightboxInstance.destroy();
          }
          window.glightboxInstance = GLightbox({ selector: '.glightbox' });
        }

        // Vimeo handling
        const vimeoIframes = document.querySelectorAll(
          "iframe[src*='player.vimeo.com']");
        vimeoIframes.forEach((iframe) => {
          const wrapper = iframe.closest(".custom_vimeo-embed");
          const player = new Vimeo.Player(iframe);
          const btn = wrapper?.querySelector("button");
          const playIcon = btn?.querySelector(".play");
          const pauseIcon = btn?.querySelector(".pause");

          if (!btn || !playIcon || !pauseIcon) return;

          // Default: paused state
          wrapper.classList.add("paused");
          playIcon.style.display = "block";
          pauseIcon.style.display = "none";

          function setState(state) {
            wrapper.classList.remove("playing", "paused");
            wrapper.classList.add(state);
            if (state === "playing") {
              playIcon.style.display = "none";
              pauseIcon.style.display = "block";
              btn.setAttribute("aria-label", "Pause");
            } else {
              playIcon.style.display = "block";
              pauseIcon.style.display = "none";
              btn.setAttribute("aria-label", "Play");
            }
          }

          player.on("loaded", () => player.pause().catch(() => {}));
          player.on("play", () => setState("playing"));
          player.on("pause", () => setState("paused"));
          player.on("ended", async () => {
            try {
              await player.setCurrentTime(0);
              await player.play();
            } catch (e) {}
          });

          btn.addEventListener("click", (e) => {
            e.stopPropagation();
            player.getPaused().then((paused) =>
              paused ? player.play() : player.pause()
            );
          });

          player.getPaused().then((paused) =>
            setState(paused ? "paused" : "playing")
          );
        });

      });
    });

  }
]);

Site URL

Required: Please provide a staging/production URL where we can see the issue

Expected Behavior

  1. The lists should combine, and the combined list should be nested onto the Service Template page OR
  2. The lists should be nested onto the Service Template page THEN both lists should be combined

Actual Behavior

The lists are being combined on the Service Items Template, but when nested it does not combine.

Video/Screenshots

Required: Please provide a short screen recording showing the issue

Hey @hello51! :waving_hand:

We can see what’s happening with your List Combine + List Nest setup. The issue comes from the execution order in v2. When List Combine runs on the Service Items Template, that combined state doesn’t carry through once the lists are nested into the parent Service Template.

To fix this, the combine operation should happen after nesting, on the parent page:

  1. On your Service Items Template: Set up the two source lists with the same instance (so they can later be combined).

    • Photos list: <div fs-list-element="list" fs-list-instance="media">
    • Videos list: <div fs-list-element="list" fs-list-instance="media">
  2. On your Service Template (main page):

  • Keep your category list as is (fs-list-element="list"), with item-link pointing to the current service item.
  • Add a single nest-target div for the combined content:
<div fs-list-element="list" fs-list-combine="media">
  <div fs-list-element="item">
    <a fs-list-element="item-link" href="[Current Page]"></a>
    <div fs-list-element="nest-target" fs-list-nest="media"></div>
  </div>
</div>

This ensures that both lists are first nested within the parent and then combined into a single, clean list. That way, the combined output is stable and ready for any custom code you want to run afterwards.

Try this approach and let us know how it works for you! If you need further help with custom code implementation, @Support-Luis or @Support-Pedro can assist you.

Hi Finn,

Thanks for the reply!

I’ve tried implementing your solution, however it seems like the nest or combine is no longer running on either template.

Service Items Template => Resource Integrated Ltd

Service Template => Content Strategy Services | Resource Integrated

Are you able to review and verify my implementation?

CC: @Support-Luis @Support-Pedro

Thanks!

Tried stripping my implementation down to the most basic, following the guide from the docs without combine and still cannot get at least list nest to work.

Really don’t know where I’m going wrong with this implementation :melting_face:

Please let me know if you’re able to review my implementation and let me know where I’m going wrong with the nest + combine set up using attributes V2 :folded_hands:t4:

@Support-Finn @Support-Luis @Support-Pedro

Was able to get the nest working for the images by placing:

fs-list-element=wrapper and fs-list-instanceon a div wrapping the link on the service item template page I assume cause I cant put the current link in a gallery collection list?

It appears that you cant nest 2 collections in a single nest target however as my videos are not being nested in the same media target as the images.

Service Template

Service Item Template

@Support-Luis @Support-Pedro

Hey @hello51!

Could you try this snippet on your page?

<script>
  window.FinsweetAttributes = window.FinsweetAttributes || [];
  window.FinsweetAttributes.push([
    'list',
    async (listInstances) => {
      const innerSelectors = [
        '[fs-list-element="list"]',
        '[role="list"]',
        '.w-dyn-items',
        '.w-dyn-list'
      ];

      for (const listInstance of listInstances) {
        const items = listInstance.items?.value || [];

        await Promise.all(items.map(async (item) => {
          try {
            if (item.nesting && typeof item.nesting.then === 'function') {
              await item.nesting;
            }

            const el = item.element;
            if (!el) return;

            const nestTargets = Array.from(
              el.querySelectorAll('[fs-list-nest], [fs-list-element="nest-target"]')
            );

            if (nestTargets.length < 2) return;

            const combinedTarget = nestTargets[0];

            for (let j = 1; j < nestTargets.length; j++) {
              const src = nestTargets[j];

              let innerList = null;
              for (const selector of innerSelectors) {
                innerList = src.querySelector(selector);
                if (innerList) break;
              }

              const rawNodes = innerList
                ? Array.from(innerList.children)
                : Array.from(src.children);

              const elementNodes = rawNodes.filter(node => node.nodeType === 1);

              elementNodes.forEach(node => {
                if (node.getAttribute && !node.getAttribute('role')) {
                  node.setAttribute('role', 'listitem');
                }
                combinedTarget.appendChild(node);
              });
            }

            combinedTarget.setAttribute('fs-list-element', 'list');
            combinedTarget.setAttribute('role', 'list');
          } catch (err) {
            console.error('❌ Error while merging nest-targets', err);
          }
        }));
      }
    }
  ]);
</script>

I made a local override, this was the result:

This script waits until the nested lists are ready. Then, for each item, it takes the first list as the main one and moves all items from the other lists into it. In the end, everything is together in one clean list.

Try this and please let me know how it goes!

Hi Pedro,

Thank you and @Support-Luis so much for helping me get this working, it’s really appreciated!

I was able to use your solution, and update it slightly to get the combined items in the same list to keep the grid layout cohesive

window.FinsweetAttributes = window.FinsweetAttributes || [];

window.FinsweetAttributes.push([
  'list',
  async (listInstances) => {
    const innerSelectors = [
      '[fs-list-element="list"]',
      '[role="list"]',
      '.w-dyn-items',
      '.w-dyn-list'
    ];
    for (const listInstance of listInstances) {
      const items = listInstance.items?.value || [];

      await Promise.all(items.map(async (item) => {
        try {
          if (item.nesting && typeof item.nesting.then === 'function') {
            await item.nesting;
          }

          const el = item.element;

          if (!el) return;

          const nestTargets = Array.from(
            el.querySelectorAll('[fs-list-nest], [fs-list-element="nest-target"]')
          );

          if (nestTargets.length < 2) return;

          const combinedTarget = nestTargets[0];

          const wrapper = combinedTarget.querySelector('.service_photo-list');

          for (let j = 1; j < nestTargets.length; j++) {
            const src = nestTargets[j];

            let innerList = null;
            for (const selector of innerSelectors) {
              innerList = src.querySelector(selector);
              if (innerList) break;
            }

            const rawNodes = innerList ?
              Array.from(innerList.children) :
              Array.from(src.children);

            const elementNodes = rawNodes.filter(node => node.nodeType === 1);

            elementNodes.forEach(node => {
              if (node.getAttribute && !node.getAttribute('role')) {
                node.setAttribute('role', 'listitem');
              }

              wrapper.prepend(node);
            });
          }

          combinedTarget.setAttribute('fs-list-element', 'list');
          combinedTarget.setAttribute('role', 'list');

          initVimeoSetup();
          initLightboxLinks();

        } catch (err) {
          console.error('❌ Error while merging nest-targets', err);
        }
      }));
    }
  }
]);

Thanks again!

1 Like