Skip to content

Latest commit

 

History

History
711 lines (637 loc) · 14.9 KB

File metadata and controls

711 lines (637 loc) · 14.9 KB
title
Listing Products

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import Stackblitz from '@site/src/components/Stackblitz';

Products are listed when:

  • Displaying the contents of a collection
  • Displaying search results

In Vendure, we usually use the search query for both of these. The reason is that the search query is optimized for high performance, because it is backed by a dedicated search index. Other queries such as products or Collection.productVariants can also be used to fetch a list of products, but they need to perform much more complex database queries, and are therefore slower.

Listing products in a collection

Following on from the navigation example, let's assume that a customer has clicked on a collection item from the menu, and we want to display the products in that collection.

Typically, we will know the slug of the selected collection, so we can use the collection query to fetch the details of this collection:

query GetCollection($slug: String!) {
  collection(slug: $slug) {
    id
    name
    slug
    description
    featuredAsset {
      id
      preview
    }
  }
}
{
  "slug": "electronics"
}
{
  "data": {
    "collection": {
      "id": "2",
      "name": "Electronics",
      "slug": "electronics",
      "description": "",
      "featuredAsset": {
        "id": "16",
        "preview": "https://demo.vendure.io/assets/preview/5b/jakob-owens-274337-unsplash__preview.jpg"
      }
    }
  }
}

The collection data can be used to render the page header.

Next, we can use the search query to fetch the products in the collection:

query GetCollectionProducts($slug: String!, $skip: Int, $take: Int) {
  search(
    input: {
      // highlight-next-line
      collectionSlug: $slug,
      groupByProduct: true,
      skip: $skip,
      take: $take }
  ) {
    totalItems
    items {
      productName
      slug
      productAsset {
        id
        preview
      }
      priceWithTax {
        ... on SinglePrice {
          value
        }
        ... on PriceRange {
          min
          max
        }
      }
      currencyCode
    }
  }
}
{
  "slug": "electronics",
  "skip": 0,
  "take": 10
}

(the following data has been truncated for brevity)

{
  "data": {
    "search": {
      "totalItems": 20,
      "items": [
        {
          "productName": "Laptop",
          "slug": "laptop",
          "productAsset": {
            "id": "1",
            "preview": "https://demo.vendure.io/assets/preview/71/derick-david-409858-unsplash__preview.jpg"
          },
          "priceWithTax": {
            "min": 155880,
            "max": 275880
          },
          "currencyCode": "USD"
        },
        {
          "productName": "Tablet",
          "slug": "tablet",
          "productAsset": {
            "id": "2",
            "preview": "https://demo.vendure.io/assets/preview/b8/kelly-sikkema-685291-unsplash__preview.jpg"
          },
          "priceWithTax": {
            "min": 39480,
            "max": 53400
          },
          "currencyCode": "USD"
        },
      ],
    },
  },
}

:::note The key thing to note here is that we are using the collectionSlug input to the search query. This ensures that the results all belong to the selected collection. :::

Here's a live demo of the above code in action:

Product search

The search query can also be used to perform a full-text search of the products in the catalog by passing the term input:

query SearchProducts($term: String!, $skip: Int, $take: Int) {
  search(
    input: {
      // highlight-next-line
      term: $term,
      groupByProduct: true,
      skip: $skip,
      take: $take }
  ) {
    totalItems
    items {
      productName
      slug
      productAsset {
        id
        preview
      }
      priceWithTax {
        ... on SinglePrice {
          value
        }
        ... on PriceRange {
          min
          max
        }
      }
      currencyCode
    }
  }
}
{
  "term": "camera",
  "skip": 0,
  "take": 10
}

(the following data has been truncated for brevity)

{
  "data": {
    "search": {
      "totalItems": 8,
      "items": [
        {
          "productName": "Instant Camera",
          "slug": "instant-camera",
          "productAsset": {
            "id": "12",
            "preview": "https://demo.vendure.io/assets/preview/b5/eniko-kis-663725-unsplash__preview.jpg"
          },
          "priceWithTax": {
            "min": 20999,
            "max": 20999
          },
          "currencyCode": "USD"
        },
        {
          "productName": "Camera Lens",
          "slug": "camera-lens",
          "productAsset": {
            "id": "13",
            "preview": "https://demo.vendure.io/assets/preview/9b/brandi-redd-104140-unsplash__preview.jpg"
          },
          "priceWithTax": {
            "min": 12480,
            "max": 12480
          },
          "currencyCode": "USD"
        }
      ]
    }
  }
}

:::tip You can also limit the full-text search to a specific collection by passing the collectionSlug or collectionId input. :::

Faceted search

The search query can also be used to perform faceted search. This is a powerful feature which allows customers to filter the results according to the facet values assigned to the products & variants.

By using the facetValues field, the search query will return a list of all the facet values which are present in the result set. This can be used to render a list of checkboxes or other UI elements which allow the customer to filter the results.

query SearchProducts($term: String!, $skip: Int, $take: Int) {
  search(
    input: {
      term: $term,
      groupByProduct: true,
      skip: $skip,
      take: $take }
  ) {
    totalItems
    // highlight-start
    facetValues {
      count
      facetValue {
        id
        name
        facet {
          id
          name
        }
      }
    }
    // highlight-end
    items {
      productName
      slug
      productAsset {
        id
        preview
      }
      priceWithTax {
        ... on SinglePrice {
          value
        }
        ... on PriceRange {
          min
          max
        }
      }
      currencyCode
    }
  }
}
{
  "term": "camera",
  "skip": 0,
  "take": 10
}

(the following data has been truncated for brevity)

{
  "data": {
    "search": {
      "totalItems": 8,
      // highlight-start
      "facetValues": [
        {
          "facetValue": {
            "id": "1",
            "name": "Electronics",
            "facet": {
              "id": "1",
              "name": "category"
            }
          },
          "count": 8
        },
        {
          "facetValue": {
            "id": "9",
            "name": "Photo",
            "facet": {
              "id": "1",
              "name": "category"
            }
          },
          "count": 8
        },
        {
          "facetValue": {
            "id": "10",
            "name": "Polaroid",
            "facet": {
              "id": "2",
              "name": "brand"
            }
          },
          "count": 1
        },
        {
          "facetValue": {
            "id": "11",
            "name": "Nikkon",
            "facet": {
              "id": "2",
              "name": "brand"
            }
          },
          "count": 2
        },
        {
          "facetValue": {
            "id": "12",
            "name": "Agfa",
            "facet": {
              "id": "2",
              "name": "brand"
            }
          },
          "count": 1
        },
        {
          "facetValue": {
            "id": "14",
            "name": "Kodak",
            "facet": {
              "id": "2",
              "name": "brand"
            }
          },
          "count": 1
        },
        {
          "facetValue": {
            "id": "15",
            "name": "Sony",
            "facet": {
              "id": "2",
              "name": "brand"
            }
          },
          "count": 1
        },
        {
          "facetValue": {
            "id": "16",
            "name": "Rolleiflex",
            "facet": {
              "id": "2",
              "name": "brand"
            }
          },
          "count": 1
        }
      ],
      // highlight-end
      "items": [
        {
          "productName": "Instant Camera",
          "slug": "instant-camera",
          "productAsset": {
            "id": "12",
            "preview": "https://demo.vendure.io/assets/preview/b5/eniko-kis-663725-unsplash__preview.jpg"
          },
          "priceWithTax": {
            "min": 20999,
            "max": 20999
          },
          "currencyCode": "USD"
        },
        {
          "productName": "Camera Lens",
          "slug": "camera-lens",
          "productAsset": {
            "id": "13",
            "preview": "https://demo.vendure.io/assets/preview/9b/brandi-redd-104140-unsplash__preview.jpg"
          },
          "priceWithTax": {
            "min": 12480,
            "max": 12480
          },
          "currencyCode": "USD"
        },
        {
          "productName": "Vintage Folding Camera",
          "slug": "vintage-folding-camera",
          "productAsset": {
            "id": "14",
            "preview": "https://demo.vendure.io/assets/preview/3c/jonathan-talbert-697262-unsplash__preview.jpg"
          },
          "priceWithTax": {
            "min": 642000,
            "max": 642000
          },
          "currencyCode": "USD"
        }
      ]
    }
  }
}

These facet values can then be used to filter the results by passing them to the facetValueFilters input

For example, let's filter the results to only include products which have the "Nikkon" brand. Based on our last request we know that there should be 2 such products, and that the facetValue.id for the "Nikkon" brand is 11.

{
  "facetValue": {
    // highlight-next-line
    "id": "11",
    "name": "Nikkon",
    "facet": {
      "id": "2",
      "name": "brand"
    }
  },
  // highlight-next-line
  "count": 2
}

Here's how we can use this information to filter the results:

:::note In the next example, rather than passing each individual variable (skip, take, term) as a separate argument, we are passing the entire SearchInput object as a variable. This allows us more flexibility in how we use the query, as we can easily add or remove properties from the input object without having to change the query itself. :::

query SearchProducts($input: SearchInput!) {
  // highlight-next-line
  search(input: $input) {
    totalItems
    facetValues {
      count
      facetValue {
        id
        name
        facet {
          id
          name
        }
      }
    }
    items {
      productName
      slug
      productAsset {
        id
        preview
      }
      priceWithTax {
        ... on SinglePrice {
          value
        }
        ... on PriceRange {
          min
          max
        }
      }
      currencyCode
    }
  }
}
{
  "input": {
    "term": "camera",
    "skip": 0,
    "take": 10,
    "groupByProduct": true,
    // highlight-start
    "facetValueFilters": [
      { "and": "11" }
    ]
    // highlight-end
  }
}
{
  "data": {
    "search": {
      "totalItems": 2,
      "facetValues": [
        {
          "facetValue": {
            "id": "1",
            "name": "Electronics",
            "facet": {
              "id": "1",
              "name": "category"
            }
          },
          "count": 2
        },
        {
          "facetValue": {
            "id": "9",
            "name": "Photo",
            "facet": {
              "id": "1",
              "name": "category"
            }
          },
          "count": 2
        },
        {
          "facetValue": {
            "id": "11",
            "name": "Nikkon",
            "facet": {
              "id": "2",
              "name": "brand"
            }
          },
          "count": 2
        }
      ],
      "items": [
        {
          "productName": "Camera Lens",
          "slug": "camera-lens",
          "productAsset": {
            "id": "13",
            "preview": "https://demo.vendure.io/assets/preview/9b/brandi-redd-104140-unsplash__preview.jpg"
          },
          "priceWithTax": {
            "value": 12480
          },
          "currencyCode": "USD"
        },
        {
          "productName": "Nikkormat SLR Camera",
          "slug": "nikkormat-slr-camera",
          "productAsset": {
            "id": "18",
            "preview": "https://demo.vendure.io/assets/preview/95/chuttersnap-324234-unsplash__preview.jpg"
          },
          "priceWithTax": {
            "value": 73800
          },
          "currencyCode": "USD"
        }
      ]
    }
  }
}

:::info The facetValueFilters input can be used to specify multiple filters, combining each with either and or or.

For example, to filter by both the "Camera" and "Nikkon" facet values, we would use:

{
  "facetValueFilters": [
    { "and": "9" },
    { "and": "11" }
  ]
}

To filter by "Nikkon" or "Sony", we would use:

{
  "facetValueFilters": [
    { "or": ["11", "15"] }
  ]
}

:::

Here's a live example of faceted search. Try searching for terms like "shoe", "plant" or "ball".

Listing custom product data

If you have defined custom fields on the Product or ProductVariant entity, you might want to include these in the search results. With the DefaultSearchPlugin this is not possible, as this plugin is designed to be a minimal and simple search implementation.

Instead, you can use the ElasticsearchPlugin which provides advanced features which allow you to index custom data. The Elasticsearch plugin is designed as a drop-in replacement for the DefaultSearchPlugin, so you can simply swap out the plugins in your vendure-config.ts file.