Learn how Scrivito CMS can help you deliver amazing digital experiences
See Scrivito CMS in action

Displaying a Product List from a Search

Displaying a Product List from a Search

Many websites offer some kind of overview, maybe an image gallery or a list of downloadable files, or specific articles.

Overviews are meant to filter content by some criterion to help website visitors quickly find what they are looking for. So, in contrast to the result of a full-text search, an overview limits itself to items of a specific type: news articles, contact persons, and the like – or products.

In this tutorial, which is based on the Scrivito Example App, we are going to define a Product object class and a simple overview widget for visitors to browse the list of products. If you want something more advanced, check out the fully fledged blog included in the example app.

For you to benefit the most from this tutorial, basic knowledge of how Scrivito’s content classes work and how they integrate with React is required.

The “Product” object class

To represent a product in Scrivito in a useful way, we need to collect its data relevant to visitors as well as internal processes like placing an order: a title and a description, a tag list (for assigning it to categories), an image and an order code. We could think of several other useful properties (availability, popularity, associated items, ...), but we’d like to keep it simple for now.

In case you’re asking yourself why we’re not considering creating a ProductWidget but have chosen to use CMS objects: This is because we want each product to be unique, meaning that all references to a product must yield the same data. Thus, we need a single CMS object for each of them. Of course, nothing speaks against creating a ProductWidget for placing a particular product object on any page. But that’s not what we’re after here.

Let’s define the Product object class, the data model we will be working with in the overview widget: Just create a folder named Product in “src/Objs”, and place the following two files into it:

src/Objs/Product/ProductObjClass.js
import * as Scrivito from 'scrivito'; Scrivito.provideObjClass('Product', { attributes: { title: 'string', description: 'html', orderCode: 'string', price: 'float', tags: 'stringlist', image: 'reference', }, });

The Product object class above declares the properties (attributes) to be available for each of its instances. The editing configuration below is for making these attributes editable in each Product’s properties and in the Content Browser (see below for how to make Product objects accessible in the Content Browser).

src/Objs/Product/ProductEditingConfig.js
import * as Scrivito from 'scrivito'; Scrivito.provideEditingConfig('Product', { title: 'Product', description: 'A product.', attributes: { title: { title: 'Title', }, description: { title: 'Description', }, orderCode: { title: 'Order code', description: 'Must start with model number', }, price: { title: 'Price in USD', }, image: { title: 'Image', }, tags: { title: 'Tags', }, }, properties: ['title', 'description', 'orderCode', 'price', 'image', 'tags'], descriptionForContent: obj => obj.get('orderCode'), hideInSelectionDialogs: true, });

Note that we are using hideInSelectionDialogs: true in the above configuration since we don’t want Product to show up in the page type selection dialog when creating a page.

Creating products via the Content Browser

To enable editors to create Product objects via the Content Browser, we need to add a filter to its configuration in “src/config/scrivitoContentBrowser.js”. There are two places where the Product class needs to be added, the defaultFilters function and the FILTER_PRESENTATIONS constant.

src/config/scrivitoContentBrowser.js
function defaultFilters() { return { _objClass: { options: { All: { title: "All", icon: "folder", query: Scrivito.Obj.all(), selected: true, }, Download: filterOptionsForObjClass("Download"), Video: filterOptionsForObjClass("Video"), Product: filterOptionsForObjClass("Product"), // … } const FILTER_PRESENTATIONS = { // … SearchResults: { title: "Search results", icon: "lens" }, Video: { title: "Videos", icon: "video" }, Product: { title: "Products", icon: "tag" }, }; // …

After that, you can select the filter in the Content Browser, click the plus icon to add a Product item, and edit its properties on the right hand side.

Providing the product view

You’ve probably noticed that the Product class above isn’t complemented with a React component for rendering its instances. Since we need to render them from within the ProductListWidget (we’re going to define further down), we’ve placed the component into a dedicated file in the “src/Components” directory, “ProductView.js”:

src/Components/ProductView.js
import * as React from 'react'; import * as Scrivito from 'scrivito'; function ProductView({ product }) { return ( <div className='row'> <div className='col-sm-2'><Scrivito.ImageTag content={ product } attribute='image' /></div> <div className='col-sm-7'> <Scrivito.ContentTag tag='h3' content={ product } attribute='title' /> <Scrivito.ContentTag tag='h5' content={ product } attribute='description' /> </div> <div className='col-sm-3'> <h3><small>$ </small>{ product.get('price') }</h3> <Scrivito.ContentTag tag='small' content={ product } attribute='orderCode' /> </div> </div> ); }; export default Scrivito.connect(ProductView);

The above component renders a Product instance as columns of a Bootstrap row. In the Bootstrap grid layout, a row consists of exactly 12 equally sized sections that can be combined to form up to 12 columns. As you can see from the CSS classes we used, the Product view generates three columns with 2 + 7 + 3 = 12 sections.

We are connecting this functional component to Scrivito using Scrivito.connect to ensure that all product data has been loaded before the component renders it.

The “ProductListWidget”

One never knows where an overview of some kind may become useful in the future, at a later stage of the website. Thus, it’s a good idea to provide the overview as a widget (instead of a page type) that editors may then place on the pages they choose.

Next to the above-mentioned advantages of using CMS objects for maintaining data objects (e.g. products, like here), there’s another truly beneficial aspect to this approach: You can use Scrivito’s search functionality to restrict the contents of your overview to exactly the kind of items you want it to include. In our case of a ProductListWidget, we simply collect all CMS objects of the Product class. We’ve placed the code in the “src/Widgets/ProductListWidget” folder:

src/Widgets/ProductListWidget/ProductListWidgetClass.js
import * as Scrivito from 'scrivito'; Scrivito.provideWidgetClass('ProductListWidget', { attributes: { bgColor: ['enum', { values: ['primary', 'secondary', 'info', 'light'] }], }, });
src/Widgets/ProductListWidget/ProductListWidgetComponent.js
import * as React from 'react'; import * as Scrivito from 'scrivito'; import ProductView from '../../Components/ProductView'; Scrivito.provideComponent('ProductListWidget', ({ widget }) => { const containerClass = `container bg-${widget.get('bgColor') || 'white'}`; const products = Scrivito.getClass('Product').all().order('title', 'asc'); if (!products) { return <p>No Products available. Create some using the Content Browser.</p>; } return ( <div className={ containerClass }> { [...products].map(product => { return <ProductView key={ product.id } product={ product } />; }) } </div> ); });
src/Widgets/ProductListWidget/ProductListWidgetEditingConfig.js
import * as Scrivito from 'scrivito'; Scrivito.provideEditingConfig('ProductListWidget', { title: 'Product List Widget', properties: ['bgColor'], attributes: { bgColor: { title: 'Background color', }, }, });

The ProductListWidget class is equipped with just one attribute, bgColor, for setting the background color of the list elements.

As mentioned, in the component, the Product instances to display are determined using a search. Afterwards, the search result is converted to array elements ([...products]) that are then rendered using the ProductView component, which has been defined earlier.

Searching for CMS objects

To find all Product instances, our component uses:

const products = Scrivito.getClass('Product').all().order('title', 'asc');

There’s no call to Scrivito.Obj.where in this statement, so why is this a search? The answer is simple if you recognize that Scrivito.getClass() returns an object class. All CMS object classes are subclasses of the Obj class to which the Obj search methods all() and where() can be applied to find all or, respectively, a subset of instances of the given class.

The result of the all() and where() methods of object classes is an ObjSearch. Basically, an ObjSearch is a search query result that can be narrowed down or ordered by applying and(), andNot(), or, respectively, order() to it. If, for example, we wanted to find only those Product instances to which a specific tag has been assigned, we could use the following query:

const products = Scrivito.getClass('Product').all() .and('tags', 'equals', tag) .order('title', 'asc');

Summarized …

With Scrivito, it’s easy to find content by specific properties (e.g. the object class) and have the result set displayed as what we called an “overview”. You can render the items in page views or in widgets, depending on the use case: If you want to enable editors to place the overview on any kind of page, provide them with a widget like the ProductListWidget above. In all other cases, a dedicated result page is the means of choice.

The searches Scrivito lets you perform using Obj.all() and Obj.where() return an ObjSearch that supports refinements and fetching the results in chunks, starting at an offset. This allows you to offer pagination.