Get Started With Coveo Atomic
I've been reading up on Coveo Atomic for a few months now. Got so into the weeds that I never realized 3 new versions had come out since talking about v1.0.4. This is a good sign, and mildly concerning. Why? Well, things change and if you are using the "latest" JavaScript version you could easily find yourself struggling with something that has already been deprecated.
Version Management
So what are your options? Well, you can select to use the latest version. As shown below:
<script type="module" src="https://static.cloud.coveo.com/atomic/latest/atomic.esm.js"></script>
<link rel="stylesheet" href="https://static.cloud.coveo.com/atomic/latest/themes/coveo.css"/>
Nothing wrong with doing so if you like living on the edge. However, if you have intentions of using this for a commercial project, I strongly suggest picking a version and sticking with it. That is if it is one that meets your needs.
Here's how you would target major versions:
<script type="module" src="https://static.cloud.coveo.com/atomic/v1/atomic.esm.js"></script>
And if you want to say target a specific minor version you can do this:
<script type="module" src="https://static.cloud.coveo.com/atomic/v1.8/atomic.esm.js"></script>
NPM Support
Now, if you want to use npm, you can indeed setup your project that way. Is it required, absolutely not. All depends on your preferences. You can access Coveo Atomic via npm via the following:
npm install @coveo/atomic
and find the resources under: /node_modules/@coveo/atomic/dist/atomic
To use it with a modular bundler, be sure to use either require('@coveo/atomic') or import '@coveo/atomic'
Let's Get To The Example
If you've followed a few of my Coveo articles you've undoubtedly noticed I try to find a fun source for my data. And thanks to previous articles, I'm utilizing a source I recently created. Yes, we're talking about The Movie DB and thanks to Feel The Power Of Coveo's Generic Rest API, we have a ton of ready-to-use data to play with.
So let's dive in and see how I achieved this in only a few hours of playing around.
Is this perfect? Absolutely not. And honestly, no where near done, but I learn by seeing examples and I wanted to get an example out to you because there are areas I struggled with getting it setup (initially). So my hope is by sharing how I achieved this I'll save you some time.
Step 1 - Setting Up The Head Tag
I did utilize Bootstrap CSS for some of the formatting, but you can't use it against the result list templates (I will explain why later). So what does the head tag look like? Here you go.
<head>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<script type="module" src="https://static.cloud.coveo.com/atomic/latest/atomic.esm.js"></script>
<link rel="stylesheet" href="https://static.cloud.coveo.com/atomic/latest/themes/coveo.css"/>
<script>
(async () => {
await customElements.whenDefined('atomic-search-interface');
const searchInterface = document.querySelector('#search');
await searchInterface.initialize({
accessToken:'*****',
organizationId: '*****'
});
searchInterface.executeFirstSearch();
})();
</script>
...
</head>
There are three sections.
- Bootstrap setup - This is just for getting some grid structure into the overall page.
- Coveo Atomic setup - Utilizing the latest version
- Search initialization - This is the meat. Here you'll see how we identify the section and initialize the
searchInterface
. If you've used Coveo JSUI this should be very familiar to you.
I will get into styling in a bit, as that's where the complexity really comes in.
Step 2 - Setting Up The Body Tag
The body tag will contain the full search interface. I've also included some basic grid structure to align things like facets and the result list.
Have a quick look below at what the HTML structure looks like. After, we will go more in-depth with what all is being used.
<div class="container">
<div class="row">
<div class="col">
<atomic-search-interface id="search">
<div class="row">
<div class="col-12">
<div class="search-bar">
<atomic-search-box></atomic-search-box>
</div>
</div>
</div>
<div class="row search-results">
<div class="col-3">
<atomic-facet-manager>
<atomic-category-facet field="mdb_genres" label="Genres" with-search></atomic-category-facet>
<atomic-facet field="mdb_release_year" label="Year" display-values-as="box" sort-criteria="occurrences" ></atomic-facet>
</atomic-facet-manager>
</div>
<div class="col-9">
<atomic-sort-dropdown>
<atomic-sort-expression label="relevance" expression="relevancy"
></atomic-sort-expression>
<atomic-sort-expression label="most-recent" expression="mdb_release_date descending"
></atomic-sort-expression>
</atomic-sort-dropdown>
<atomic-result-list display="list" image-size="small" fields-to-include="mdb_release_year,mdb_release_year,mdb_poster_url,mdb_description,mdb_genres,source, mdb_release_date,language, filetype, ytthumbnailurl">
<atomic-result-template>
<template>
...
<div class="movie-result">
<atomic-result-section-visual class="movie-result_poster">
<atomic-result-image field="mdb_poster_url"></atomic-result-image>
</atomic-result-section-visual>
<div class="movie-result_details">
<atomic-result-link class="movie-result_title"></atomic-result-link>
<atomic-result-text class="movie-result_description" field="mdb_description"></atomic-result-text>
<atomic-result-multi-value-text field="mdb_genres" delimiter=" "></atomic-result-multi-value-text>
<div class="movie-result_release-date">Release Date: <atomic-result-date field="mdb_release_date"></atomic-result-date></div>
</div>
</div>
</template>
</atomic-result-list-template>
</atomic-result-list>
</div>
</div>
<div class="row col pagination">
<atomic-load-more-results></atomic-load-more-results>
</div>
</atomic-search-interface>
</div>
</div>
</div>
Let's break it down. Again, we will touch on styling near the end.
atomic-search-interface
This is the primary wrapper for the search experience. We used the #search
identifier which is how the initialization points to and sets up. This also allows for multiple search areas on a page if needed.
atomic-search-box
As you might expect, this is the search box. Not doing anything special here, but obviously you can do more if desired using type ahead, query suggestions, etc.
atomic-facet-manager
This is the wrapper area for all facets.
atomic-facet & atomic-category-facet
Here we're trying to represent some of the facet information in different ways.
The atomic-facet
is your tried and true Coveo facet. You can display it in a few different forms, here I've done it as a boxes.
The atomic-category-facet
is the facet you're most likely to see on a search page. Here we're using it to display all the genres present in our data.
atomic-sort-dropdown
Pretty self explanatory but this is where we create a sort drop-down menu with the options identified by atomic-sort-expression
. In our case we have Relevancy and sort by Release Date.
atomic-result-list
This is where, honestly, I struggled a bit. Not so much on setting up the appropriate hierarchy but the styling.
Inside the atomic-result-list
tag we immediately have atomic-result-template
and template
. This is where we are defining how the result looks. And yes, you can have multiple templates based upon conditions. Because I only have a single set of data, I've chosen to just use one but in a future article we will get a bit more creative.
atomic-result-section-visual
This is the area of the result that provides visual information, such as our movie poster.
atomic-result-image
We use this tag to get our movie poster to show. The field value is the URL to the image in question.
atomic-result-link
This is the title and the link to the page (if we had one) for our data.
atomic-result-text
This allows us to show the movie description. It's a simple field text display.
atomic-result-multi-value-text
As you might expect, this creates a list from a multi-facet. e.g. Action; Drama;
Styling - What You've Come Here For
Let's be honest here. What I've gone through above you probably already knew. What you want to understand is how I got the above to look like what's in the image above. Looking back there's nothing crazy going on but there's an important concept to understand. The Shadow DOM.
What Is The Shadow DOM?
You can find a more in-depth description of how the Shadow DOM operates here but in general it's a way of encapsulating aspects of the DOM and essentially "shielding" them from outside influence. In terms of Coveo Atomic, it's used to prevent outside CSS from interfering with the operation of a search page. Ever built a Coveo search page in JSUI and someone who styles the whole site changed how an <li>
tag is styled? It hurts, doesn't it. Well Coveo is trying to save you the pain by utilizing the Shadow DOM to protect the pieces of a search page from being broken.
Where Does Styling Go?
So what I've found, and keep in mind this could very well change as Atomic progresses, that it happens in two main areas.
Inside The <head> Tag
This is your primary CSS. But when it comes to targeting portions of the DOM we need to understand that not everything can be styled directly. If you want a full level of capabilities you're going to want to use Headless.
Let's look at an example.
:root{
--atomic-font-family: 'Montserrat', sans-serif;
--atomic-text-base: 1.1rem;
line-height:1.2rem;
}
atomic-search-box::part(input){
width:800px;
}
atomic-facet::part(values){
flex-wrap: wrap;
display:flex;
flex-direction:row;
justify-content: space-between;
}
atomic-facet::part(value-box){
min-width:55px;
display:flex;
}
atomic-sort-dropdown{
margin-bottom: 20px;
}
When it comes to styling the Atomic DOM you will be primarily focusing on using the ::part
functionality. If you inspect the DOM using Dev Tools, you'll see Coveo has used the part
attribute throughout, but not at every single level. In order to style the structure of your search interface you'll need to use these to target specific elements of your page.
The other thing to know is how Coveo utilizes CSS variables as a way of "theming" your search page. By altering specific CSS variables, you can very quickly transform a page without complex CSS magic. You can find a full list of variables here
An important thing to note. You CANNOT style a result list inside the <head> tag. Maybe it will change in the future? But right now, let me save you the trouble. It turns out not to be terribly complicated, but because of the use of the Shadow DOM, you have to place the result styling in a specific location. Inside the template
tag within the atomic-result-template
itself.
Styling The Result List
As mentioned, you have to place result specific styling inside the template
tag. Let me show you.
<atomic-result-template>
<template>
<style>
.movie-result{
display:flex;
flex-direction: row;
}
.movie-result_poster{
max-height:200px;
display:flex;
margin-right:20px;
max-width:25%;
min-width:25%;
}
.movie-result_poster img{
object-fit: contain;
overflow: hidden;
}
.movie-result_details{
display:flex;
flex-direction: column;
}
.movie-result_title{
display:flex;
font-size:1.5rem;
font-family:'Lato', sans-serif;
line-height:1.75rem;
}
.movie-result_description{
display:flex;
color:#999;
}
.movie-result_release-date{
display:flex;
color:#999;
margin-top:15px;
}
atomic-result-multi-value-text::part(result-multi-value-text-value){
background-color:#ddd;
border-radius:5px;
padding:10px;
margin-top:15px;
margin-right:15px;
}
atomic-result-multi-value-text::part(result-multi-value-text-separator)::before{
content:'';
}
</style>
...
</template>
</atomic-result-template>
Here we can do traditional CSS targeting aspects of our custom structure along with targeting specific areas of Atomic's deeper Shadow DOM.
In Summary
I've covered a ton of ground here. Expect more articles diving deeper into things like the facets, result list capabilities, and all the other things that Atomic can do really, really easily.