Implement folding

This is for:

Developer

Coveo Atomic lets you display search results that have a parent-child relationship with any level of nesting. This article explains how to build hierarchical result lists using the atomic-folded-result-list component.

Prerequisites

Before pushing any data to the Coveo index, make sure that the data you’re working with has established parent-child relationships. Some data may already have these relationships when you collected it. If not, you’ll need to build a relationship graph yourself.

Note

You can’t create parent-child relationships via the Coveo Administration Console, which means you need to implement such data processing in your own infrastructure.

Once your data has established parent-child relationships, you need to push the data to your Coveo organization and then create the proper folding fields for the target items.

Main features

The atomic-folded-result-list component is similar to the atomic-result-list component, but provides extra functionality. This component requires no extra configuration apart from specifying the three folding fields:

The atomic-folded-result-list requires one or more atomic-result-template components to display the results. This result template can include an atomic-result-children component with nested atomic-result-children-template components to render the child results.

The atomic-result-children component also offers two slots named before-children and after-children. They’re available via the attributes slot="before-children" and slot="after-children". These slots let you insert a list of children only when children are available for that specific result. The slots let you insert a component inside atomic-result-children but outside any templates.

Child elements

The following sample shows the basic structure to fold results with one child.

<atomic-folded-result-list>
  <atomic-result-template>
    <!-- RESULT template defined here -->
    <atomic-result-children>
      <atomic-result-children-template>
        <!-- CHILD template defined here -->
      </atomic-result-children-template>
    </atomic-result-children>
  </atomic-result-template>
</atomic-folded-result-list>
Important

The atomic-result-children-template component doesn’t let you leave templates empty. You have to specify elements inside of them.

Grandchild elements

To render grandchildren, you need to add an extra layer, as in the following example:

<atomic-folded-result-list>
  <atomic-result-template>
    <!-- RESULT template defined here -->
    <atomic-result-children>
      <atomic-result-children-template>
        <!-- CHILD template defined  here -->
          <atomic-result-children>
            <atomic-result-children-template inherit-templates="true"> 1
              <!-- GRANDCHILD template defined here -->
            </atomic-result-children-template>
          </atomic-result-children>
      </atomic-result-children-template>
    </atomic-result-children>
  </atomic-result-template>
</atomic-folded-result-list>
1 If you want the grandchildren to be the same as the children, you can set inherit-templates attribute to true on the grandchild level and omit the templates. With that option, you can either leave the atomic-result-children-template empty or still specify its own templates which will take priority over children (or inherited) templates.

The grandchild rendering depends on templates and the value of the inherit-templates option. For example:

  • The child defines template 1 and template 2; the grandchild defines template 3 and has inherit-templates="true". The grandchild will use the templates in the following order:

    1. Template 3 (defined at the grandchild level)

    2. Template 1 and template 2 (both defined at the child level, in this order)

  • The child defines template 1 and template 2; the grandchild defines template 3 and inherit-templates is set to "false" or not specified. The grandchild will only use template 3 and ignore the other two.

Comprehensive example

The following example uses all the features mentioned in the previous sections.

Note that templates are only available within the scope in which they’re defined. For example, an atomic-result-children-template child defined inside an atomic-result-template will only be available for results that match both templates. An atomic-result-children-template that’s a child of another atomic-result-children-template will similarly only be available for results that match both these templates.

If you need to reuse templates, you can define them outside of the scope of the results and refer to them by ID anywhere you want to place them.

<atomic-folded-result-list>
  <atomic-result-template must-match-sourcetype="YouTube">
    <template> 1
      <img src="https://www.youtube.com/s/desktop/406de18b/img/favicon_32x32.png" class="thumbnail" />
      <atomic-result-link></atomic-result-link>
    </template>
  </atomic-result-template>
  <atomic-result-template must-match-sourcetype="Twitter">
    <template>
      <img src="https://cdn.cms-twdigitalassets.com/content/dam/developer-twitter/images/Twitter_logo_blue_32.png" class="thumbnail" />
      <atomic-result-link></atomic-result-link>
      <atomic-result-fields-list>
        <atomic-result-text field="excerpt"></atomic-result-text>
        <span class="field-label"><atomic-text value="author"></atomic-text>:</span>
        <atomic-result-text field="author"></atomic-result-text>
      </atomic-result-fields-list>
      <div class="thread">
        <atomic-result-children> 2
          <atomic-result-children-template>
            <template>
              <span class="field-label"><atomic-text value="author"></atomic-text>:</span>
              <atomic-result-text field="author"></atomic-result-text>
              <atomic-result-text field="excerpt"></atomic-result-text>
              <div class="thread">
                <atomic-result-children inherit-templates>
                  3
                </atomic-result-children>
              </div>
            </template>
          </atomic-result-children-template>
        </atomic-result-children>
      </div>
    </template>
  </atomic-result-template>
  <atomic-result-template must-match-sourcetype="Slack">
    <template>
      <img src="https://cdn.bfldr.com/5H442O3W/at/pl546j-7le8zk-6gwiyo/Slack_Mark.svg?auto=webp&format=svg&width=32&height=32" class="thumbnail" />
      <atomic-result-link></atomic-result-link>
      <atomic-result-fields-list>
        <atomic-result-text field="excerpt"></atomic-result-text>
        <span class="field-label"><atomic-text value="author"></atomic-text>:</span>
        <atomic-result-text field="author"></atomic-result-text>
      </atomic-result-fields-list>
      <div class="thread">
        <atomic-result-children> 4
          <p slot="before-children">Available files:</p> 5
          <atomic-result-children-template must-match-type="uploadedFile">
            <template>
              <atomic-result-icon class="icon"></atomic-result-icon>
            </template>
          </atomic-result-children-template>
        </atomic-result-children>
      </div>
    </template>
  </atomic-result-template>
</atomic-folded-result-list>
1 YouTube results will ignore any children as no atomic-result-children components are present here.
2 Twitter results will show children and grandchildren.
3 Grandchild results will inherit the templates from their parent (child results).
4 Slack results will only show children that are uploaded files and ignore grandchildren.
5 The p element will only be rendered when a Slack result has children.

Folding in rendering functions

The atomic-folded-result-list component allows you to set the templates by passing it to a renderingFunction that will return a FoldedResult, which you can then use to render its children or grandchildren as well.

The following is an example of such usage in Atomic React:

import React, {FunctionComponent} from 'react';

import {
  AtomicFormatCurrency,
  AtomicResultBadge,
  AtomicResultFieldsList,
  AtomicResultImage,
  AtomicResultLink,
  AtomicResultMultiValueText,
  AtomicResultNumber,
  AtomicResultPrintableUri,
  AtomicResultRating,
  AtomicResultSectionBadges,
  AtomicResultSectionBottomMetadata,
  AtomicResultSectionEmphasized,
  AtomicResultSectionExcerpt,
  AtomicResultSectionTitle,
  AtomicResultSectionTitleMetadata,
  AtomicResultSectionVisual,
  AtomicResultText,
  AtomicText,
  FoldedResult,
  AtomicResultSectionChildren,
  AtomicFoldedResultList,
  AtomicResultChildren,
  AtomicResultChildrenTemplate,
} from '@coveo/atomic-react';
import {AtomicApp} from './AtomicApp';

export const Folded: FunctionComponent = () => {
  return (
    <AtomicApp
      accessToken="xx564559b1-0045-48e1-953c-3addd1ee4457"
      organizationId="searchuisamples"
    >
      <AtomicFoldedResultList
        fieldsToInclude={["ec_price","ec_rating","ec_images","ec_brand","cat_platform","cat_condition","cat_categories","cat_review_count","cat_color"]}
        imageSize="large"
        template={MyTemplate}
      />
    </AtomicApp>
  );
};

function MyTemplate(result: FoldedResult) {
  return (
    <>
      <AtomicResultSectionBadges>
        <AtomicResultBadge field="ec_brand" />
      </AtomicResultSectionBadges>
      <AtomicResultSectionVisual>
        <AtomicResultImage field="ec_images" />
      </AtomicResultSectionVisual>
      <AtomicResultSectionTitle>
        <AtomicResultLink />
      </AtomicResultSectionTitle>
      <AtomicResultSectionTitleMetadata>
        <AtomicResultRating field="ec_rating" />
        <AtomicResultPrintableUri maxNumberOfParts={3} />
      </AtomicResultSectionTitleMetadata>
      <AtomicResultSectionEmphasized>
        <AtomicResultNumber field="ec_price">
          <AtomicFormatCurrency currency="USD" />
        </AtomicResultNumber>
      </AtomicResultSectionEmphasized>
      <AtomicResultSectionExcerpt>
        <AtomicResultText field="ec_shortdesc" />
      </AtomicResultSectionExcerpt>
      <AtomicResultSectionChildren>
        <AtomicResultChildren> 1
          <AtomicResultChildrenTemplate>
            <template>
              <AtomicResultLink />
            </template>
          </AtomicResultChildrenTemplate>
        </AtomicResultChildren>
      </AtomicResultSectionChildren>
      <AtomicResultSectionBottomMetadata>
        <AtomicResultFieldsList>
          {result.result.raw.cat_platform && (
            <>
              <span className="field-label">
                <AtomicText value="Platform" />
              </span>
              <AtomicResultText field="cat_platform" />
            </>
          )}
          {result.result.raw.cat_condition && (
            <>
              <span className="field-label">
                <AtomicText value="Condition" />
              </span>
              <AtomicResultText field="cat_condition" />
            </>
          )}
          {result.result.raw.cat_categories && (
            <>
              <span className="field-label">
                <AtomicText value="Tags" />
              </span>
              <AtomicResultMultiValueText field="cat_categories" />
            </>
          )}
        </AtomicResultFieldsList>
      </AtomicResultSectionBottomMetadata>
    </>
  );
}
1 This is where you define children.

Although you can still use the Atomic components to render children, FoldedResult offers you the children prop where you have access to the children results in the main function too.