Custom Result Card Code Examples | Yext Hitchhikers Platform
Box Shadow Around Card on Hover
Add the following to the answers.scss
file:
.[[card name]] {
transition: box-shadow 0.3s;
&:hover {
box-shadow: 0 0 11px rgba(33, 33, 33, 0.2);
}
}
Universal Search Results with Only Top and Bottom Borders
Add the following to the answers.scss
file:
.HitchhikerResultsStandard-title {
border-left: none;
border-right: none;
border-top: none;
border-bottom: 1px solid #c5bbb1;
}
.HitchhikerResultsStandard-titleLabel {
border-left: none;
border-right: none;
border-top: none;
border-bottom: none;
}
.HitchhikerResultsStandard-Card {
border-left: none;
border-right: none;
border-top: none;
border-bottom: 1px solid #c5bbb1;
padding-bottom: 30px;
padding-top: 7px;
}
.HitchhikerResultsStandard-viewMore {
border-left: none;
border-right: none;
border-bottom: none;
border-top: none;
}
.yxt-Card {
border-left: none;
border-right: none;
border-top: none;
border-bottom: 1px solid #c5bbb1;
}
Prominent Image Unification
The object-fit
properties help define the behavior of the image if it isn’t inherently the fixed dimensions.
Add the following to the answers.scss
file:
.HitchhikerProductProminentImage-imgWrapper {
justify-content: center;
padding: 0.5rem;
}
.HitchhikerProductProminentImage-img {
width: auto;
height: 200px;
object-fit: cover;
object-position: center;
}
This will standardize your images to all have a height of 200px, with a .5rem border around your images.
Add Circular Headshots
The best way to accomplish this is to do so via the object-fit
property. Learn more about this property
here
.
The below CSS would make the images circular, focusing on the center of the image, with a standard size.
Add the following to the answers.scss
file:
.HitchhikerProductProminentImage-img {
width: 100%;
height: 200px;
object-fit: cover;
object-position: center;
border-radius: 50%;
padding: 1rem;
}
If you would like to differentiate certain properties for the Desktop and Mobile experiences you can use media queries :
.HitchhikerProductProminentImage-img {
width: 100%;
height: 200px;
object-fit: cover;
object-position: center;
border-radius: 50%;
padding: 1rem;
@media only screen and (max-width: 768px) {
border-radius: 0px;
padding: 0px;
}
}
Add Email to Cards
In order to do this the right way, we’ll need to do three things:
1. Update the custom card’s component.js
to pass through the Email field, as well as the details needed to successfully fire an Analytics event.
email: profile.emails ? profile.emails[0] : null,
emailEventOptions: this.addDefaultEventOptions(),
The email
attribute passes in the first email address of the Emails field (if populated), otherwise returns null. Modify this to align with the field that stores your email addresses.
The emailEventOptions
attribute just passes the standard data (entity ID, universal vs. vertical search, etc.) that allows us to fire an Analytics event.
2. Update the custom card’s template.hbs
to add a section for Email that correctly formats the link.
First, we’ll create an {{> email}}
partial. Add the below within the template.hbs
file, preferably below the {{#*inline 'phone'}}
partial.
{{#*inline 'email'}} {{#if card.email}}
<div class="HitchhikerLocationStandard-email">
<a
href="mailto:{{card.email}}"
data-eventtype="EMAIL"
data-eventoptions="{{json card.emailEventOptions}}"
target="_blank"
>
{{card.email}}
</a>
</div>
{{/if}} {{/inline}}
Here, we’ve added a new class HitchhikerLocationStandard-email
that allows us to style this email in the future. We’re then adding a link - to create an email link, simply add mailto:
in front of the email address. We’re also passing data-eventtype
and data-eventoptions
so that an EMAIL event fires when a user clicks on the link. Lastly, the text of the link will be the email itself, enclosed within the <a>
tag.
Now that we’ve defined the partial, we need to reference it so it’s placed on the card. If you’re forking from a location-standard or professional-standard card, you can navigate to the contactInfo
partial in your file and add the email partial underneath the address & phone.
{{#* inline "contactInfo"}}
<div class="HitchhikerLocationStandard-contactInfo">
{{#if (any card.phone card.address card.email)}}
<div class="HitchhikerLocationStandard-core">
{{> address }}
{{> phone }}
{{> email }}
</div>
{{/if}}
You’ll see that we both added the card.email
in the #if
statement, as well as added the partial below the address and phone partials.
3. Optional - add CSS to update the link styling
Add the following to the answers.scss
file:
.HitchhikerLocationStandard-email {
color: var(--yxt-color-brand-primary);
margin-top: 0.5rem;
a {
text-decoration: underline;
&:hover {
text-decoration: none;
}
}
}
Display a List without Bullets
You will utilize list-style-type
to display a list on cards and exclude the bullet points.
Add the following to the answers.scss
file:
.HitchhikerMenuItemStandard-listItems {
list-style-type: NONE;
}
Add a Conditional CTA Based on Boolean
You can easily do this by adding a boolean to your component.js
file, and modifying the Handlebars template to only show the third CTA if that boolean evaluates to true.
1. Update component.js
The first step is to pass the information about whether a provider is accepting patients. Add an attribute in your component.js
file to store whether or not a provider is accepting new patients. If you use the profile field, when this is marked as ‘Yes’, the boolean will evaluate to true; if it’s marked as false or unpopulated, it will evaluate to false.
acceptingNewPatients: profile.acceptingNewPatients,
You’ll also need to provide the data mapping for the third CTA.
CTA3: { // The tertiary call to action for the card
label: 'Book Appointment',
iconName: 'calendar',
url: profile.c_primaryCTA.link,
target: '_top',
eventType: 'BOOK_APPOINTMENT',
eventOptions: this.addDefaultEventOptions(),
// ariaLabel: '',
}
2. Update template.hbs
The next step is to use this boolean to only show the tertiary CTA when acceptingNewPatients = true
.
We can do this using an if statement in Handlebars. We’ll check if card.acceptingNewPatients
evaluates to true; if so, we’ll add the tertiary CTA. You can see the modifications to the CTA partial below:
{{#*inline 'ctas'}}
{{#if (any (all card.CTA1 card.CTA1.url) (all card.CTA2 card.CTA2.url)(all card.CTA3 card.CTA3.url))}}
<div class="HitchhikerLocationStandard-ctasWrapper">
{{> CTA card.CTA1 ctaName="primaryCTA" }}
{{> CTA card.CTA2 ctaName="secondaryCTA" }}
{{#if card.acceptingNewPatients}}
{{> CTA card.CTA3 ctaname="tertiaryCTA"}}
{{/if}}
</div>
{{/if}}
{{/inline}}
This will hide the CTA if acceptingNewPatients
is not populated or false, and show it if it’s true.
Center CTAs Vertically
With the default Handlebars template, the CTAs container will align with the details container on a card. If you’d like to change the alignment, you’ll need to modify the card template slightly.
If you want to shift the CTAs up to align them vertically within the card, rather than align them with the details container, you can move the title and subtitle into the ‘info’ div
.
1. Update template.hbs
<div class="HitchhikerProfessionalStandard {{cardName}}">
{{> image }}
<div class="HitchhikerProfessionalStandard-body">
<div class="HitchhikerProfessionalStandard-contentWrapper">
<div class="HitchhikerProfessionalStandard-info">
{{> title }}
{{> subtitle }}
{{> details }}
{{> list }}
{{> phone }}
</div>
{{> ctas }}
</div>
</div>
</div>
2. Add Styling
You can ensure the contentWrapper div extends the whole height of the card, and use justify-content
and align-self
to align the CTAs to the middle of the card.
Add the following to the answers.scss
file:
.HitchhikerProfessionalStandard-contentWrapper {
height: 100%;
}
.HitchhikerProfessionalStandard-ctasWrapper {
justify-content: center;
align-self: center;
}
Fix CTAs to Bottom of Card
An easy way to make sure the CTAs are fixed to the bottom of the card is to use Flexbox to our advantage.
Assuming you’re using the product-prominentImage
card (or a forked version of it), add the following to the answers.scss
file:
.HitchhikerProductProminentImage {
&-body {
height: 100%;
display: flex;
flex-direction: column;
}
&-contentWrapper {
height: 100%;
justify-content: space-between;
}
}
This accomplishes the following:
- Specifies the
.HitchhikerProductProminentImage-body
class to useflex
and take up the full height of the card available after the image - Allows the
.HitchhikerProductProminentImage-contentWrapper
class to take up the full height of the container, and specifies the spacing between the description & the CTAs to bespace-between
, forcing the CTAs to the end of the card.
If you don’t have a description, the following CSS may help you as it will set the top margin to fix the CTAs container to the bottom of the card.
.HitchhikerProductProminentImage-ctasWrapper {
margin-top: auto;
}
Add Dynamic Text Highlighting to a Result Card
When a user inputs a query and Search finds that a certain field is highlighted, that information can be displayed on result cards with the isHighlighted
utility function and highlightField
formatter.
When you add these to a card, users will see text formatted as "[[field name]]" mentions [[highlighted text]]
. For example, if a users searches “Pulitzer Prize” to see which authors have received that award, any authors that are returned will have text on their result card noting that the text in the query matches data in the awards field.
This is to cover the cases where you have keyword search or document search on a field, but it’s not displayed on your card by default.
To do this:
- Create a custom card where you’d like the dynamic highlighting displayed. If you have a custom card already created that you want to add this to, then move to the next step.
In the card’s component.js file, navigate to
dataForRender
object and add the followingdynamicHighlights
variable://check if "c_awards" is highlighted var dynamicHighlights = []; if (HitchhikerJS.isHighlighted("c_awards", profile.d_highlightedFields)) { const fullHighlight = profile.d_highlightedFields["c_awards"]; dynamicHighlights.push({ "fieldName": "Awards", "value": Formatter.highlightField(fullHighlight.value, fullHighlight.matchedSubstrings) }); }
If you want to check a different field for text matches to highlight, you will update the references to “c_awards” in the code block as well as update
fieldName
. For example, if you want to return text from thename
field, you would use the following://check if "c_awards" is highlighted var dynamicHighlights = []; if (HitchhikerJS.isHighlighted("name", profile.d_highlightedFields)) { const fullHighlight = profile.d_highlightedFields["name"]; dynamicHighlights.push({ "fieldName": "Name", "value": Formatter.highlightField(fullHighlight.value, fullHighlight.matchedSubstrings) }); }
List fields also work very similarly. Instead, you’d need to iterate through the items before adding them to your
dynamicHighlights
array. Here’s an example of adding dynamic highlights with the “Languages” built-in field:if (profile.d_highlightedFields["languages"]) { const fullHighlight = profile.d_highlightedFields["languages"]; for (const item in fullHighlight) { if (fullHighlight[item].matchedSubstrings && fullHighlight[item].matchedSubstrings.length > 0) { dynamicHighlights.push({ "fieldName": "Languages", "value": Formatter.highlightField(fullHighlight[item].value, fullHighlight[item].matchedSubstrings) }); } }; }
In the same component.js file, add a new property,
highlightedField
, to the return statement. Set it up to reference the newdynamicHighlights
variable you just created:highlightedField: dynamicHighlights,
When it is complete, the
dataForRender
in your component.js should look something like this:dataForRender(profile) { //check if "c_awards" is highlighted var dynamicHighlights = []; if (HitchhikerJS.isHighlighted("c_awards", profile.d_highlightedFields)) { const fullHighlight = profile.d_highlightedFields["c_awards"]; dynamicHighlights.push({ "fieldName": "Awards", "value": Formatter.highlightField(fullHighlight.value, fullHighlight.matchedSubstrings) }); } return { title: profile.name, // The header text of the card url: profile.website || profile.landingPageUrl, target: '_top', // If the title's URL should open in a new tab, etc. titleEventOptions: this.addDefaultEventOptions(), details: profile.description, // The text in the body of the card highlightedField: dynamicHighlights, //new showMoreDetails: { showMoreLimit: 750, // Character count limit showMoreText: 'Show more', // Label when toggle will show truncated text showLessText: 'Show less' // Label when toggle will hide truncated text } }; }
Now add the highlights to the card’s template.js file. Start by adding this code to the bottom of the file:
{{#*inline 'highlightedField'}} {{#if card.highlightedField}} <ul class="highlights"> {{#each card.highlightedField}} <li class="highlight">"{{this.fieldName}}" mentions {{{this.value}}}</li> {{/each}} </ul> {{/if}} {{/inline}} {{!-- other partials below --}}
At the top of your file, there is a code block with the structure of the card. You will see things like title, subtitle, and details in the order that appear on the card. Add
{{> highlightedField}}
in the position you want it to display on the card. In most cases you will want to add it below the{{> details}}
like so:<div class="HitchhikerStandard {{cardName}}"> {{> image }} <div class="HitchhikerStandard-body"> {{> title }} {{> subtitle }} <div class="HitchhikerStandard-contentWrapper"> <div class="HitchhikerStandard-info"> {{> details }} {{> highlightedField }} </div> {{> ctas }} </div> </div> </div>
Use Conditional Placeholder Images
One common issue we’ve seen is gaps in data, especially for images such as for headshots.
This example walks through using the JavaScript conditional operator to use placeholder images for any agents missing a headshot image, where the placeholder images differ by gender. In other words, the logic is as follows:
- If the
Headshot
field is populated, use that image. - If the
Headshot
field is empty and theGender
field is set to Female, use the female placeholder image. - Otherwise, fall back to the generic placeholder image.
Learn more about null checks .
Add the following image
property in the component.js
file of the desired forked card:
return {
title: profile.name, // The header text of the card
subtitle: profile.c_area, // The sub-header text of the card
image: profile.headshot ? Formatter.image(profile.headshot).url : (profile.gender === 'Female' ? 'https://a.mktgcdn.com/p-sandbox/y0iJHYFeCERihQg0qv7j88TBeEnpHq-v_BLyHiDXOb8/142x200.png' : 'https://a.mktgcdn.com/p-sandbox/6DD2AHZA1kbe8M9ZQGW_JOwXFyWNnRTMDJXIGxvQK-4/321x450.jpg'),
...
}