Direct Answers | Yext Hitchhikers Platform
The DirectAnswer
component shows a Direct Answer to a query. When we are confident that a search has one answer, we will surface a Direct Answer box at the top of the results.
How Direct Answer Formatting Works by Default
As we went over in the
Direct Answers
unit, Yext Search has two types of Direct Answers: Featured Snippet and Field Value. Each of these has its own cardType
as you can see below since they each surface different types of content.
In the Theme, we have the following card files:
Field Value Direct Answers (surfacing from structured data in Content)
- directanswers-card > allfields-standard > component.js
- directanswers-card > allfields-standard > template.hbs
Featured Snippet Direct Answers (surfacing from unstructured data in Content)
- directanswers-card > documentsearch-standard > component.js
- directanswers-card > documentsearch-standard > template.hbs
Note that for the Field Value Direct Answers, in Jambo, we provide you with some default styling by field type. In the file, we will outline all field types and their corresponding default formatting and direct answer card layout. This means the files are long and can be a bit intimidating – we recommend using ctrl-find to find what you’re looking for more easily.
Overriding Direct Answer Formatting
We’ve applied formatting and settings that we think works best so that you don’t have to do the heavy-lifting. That said, sometimes you just want something different and we provide that flexibility too.
To modify the settings of Direct Answers, you would add the DirectAnswer
component in ComponentSettings
for your Search page and change the cardType
to your forked card.
"DirectAnswer": {
"types": {
"FEATURED_SNIPPET": {
"cardType": "documentsearch-standard"
},
"FIELD_VALUE": {
"cardType": "allfields-standard"
}
}
},
To modify the styling of Direct Answers, use Jambo Commands > Add Direct Answer Card. This will fork the default card in the theme and create a new set of files in a new top-level directory as you learned in the Customize Result Cards unit.
In general, we don’t recommend forking the default formatting unless you really need to.
Featured Snippet Direct Answers
The most common use case for forking the documentsearch-standard
card is adding a CTA. Like with result cards, you can adjust the data mappings of a CTA button to be either hardcoded or entity field values.
When referencing an entity field value on a regular result card, we map to the entity’s desired field using the syntax profile.fieldName
. Due to differences in the content returned from the API for Featured Snippets, the syntax for mapping to a field changes to relatedItemData.fieldValues.fieldName
.
Below is an example of a CTA that has been added to the component.js
file for a forked Featured Snippet Direct Answer Card and mapped to the Primary CTA field.
CTA: {
label: relatedItemData.fieldValues.c_primaryCTA ? relatedItemData.fieldValues.c_primaryCTA.label : null, // The CTA's label
url: Formatter.generateCTAFieldTypeLink(relatedItemData.fieldValues.c_primaryCTA), // The URL a user will be directed to when clicking
target: linkTarget, // Where the new URL will be opened
eventType: 'CTA_CLICK', // Type of Analytics event fired when clicking the CTA
eventOptions: this.addDefaultEventOptions({ fieldName: 'snippet' }) // The event options for CTA click analytics
}
Field Value Direct Answers
When you fork the allfields-standard
card, as mentioned, you can choose to specify the formatting by field type (default) OR by specific field. We make this possible by using a switch function in the javascript.
component.js file
switch (answer.fieldType) {
case 'url':
case 'complex_url':
case 'ios_app_url':
case 'android_app_url':
case 'facebook_url':
if (isArray) {
arrayValue = answer.value.map((value) => ({
url: value,
label: value
}
));
} else {
regularValue = {
url: answer.value,
label: answer.value
};
}
value = isArray ? arrayValue : regularValue;
break;
case 'email':
if (isArray) {
arrayValue = answer.value.map((value) => ({
url: `mailto:${value}`,
label: value,
}
));
} else {
regularValue = {
url: `mailto:${answer.value}`,
label: answer.value,
};
}
value = isArray ? arrayValue : regularValue;
break;
case 'instagram_handle':
if (isArray) {
arrayValue = answer.value.map((value) => ({
url: `https://instagram.com/${value}`,
label: `@${value}`,
}
));
} else {
regularValue = {
url: `https://instagram.com/${answer.value}`,
label: `@${answer.value}`,
};
}
value = isArray ? arrayValue : regularValue;
break;
case 'twitter_handle':
if (isArray) {
arrayValue = answer.value.map((value) => ({
url: `https://twitter.com/${value}`,
label: `@${value}`,
}
));
} else {
regularValue = {
url: `https://twitter.com/${answer.value}`,
label: `@${answer.value}`,
};
}
value = isArray ? arrayValue : regularValue;
break;
case 'phone':
if (isArray) {
arrayValue = answer.value.map((value) => ({
url: Formatter.phoneLink({mainPhone: value}),
label: Formatter.nationalizedPhoneDisplay({mainPhone: value}),
}
));
} else {
regularValue = {
url: Formatter.phoneLink({mainPhone: answer.value}),
label: Formatter.nationalizedPhoneDisplay({mainPhone: answer.value}),
};
}
value = isArray ? arrayValue : regularValue;
break;
case 'address':
if (isArray) {
arrayValue = answer.value.map((value) => Formatter.address({address: value}));
} else {
regularValue = Formatter.address({address: answer.value});
}
value = isArray ? arrayValue : regularValue;
break;
case 'hours':
if (isArray) {
arrayValue = answer.value.map((value) => `<div>${Formatter.openStatus({hours: value})}</div>`);
} else {
regularValue = `<div>${Formatter.openStatus({hours: answer.value})}</div>`;
}
value = isArray ? arrayValue : regularValue;
break;
case 'decimal':
if (isArray) {
arrayValue = answer.value.map((value) => value.toLocaleString());
} else {
regularValue = answer.value.toLocaleString();
}
value = isArray ? arrayValue : regularValue;
break;
case 'rich_text':
if (isArray) {
arrayValue = answer.value.map((value) => ANSWERS.formatRichText(value));
} else {
regularValue = ANSWERS.formatRichText(answer.value);
}
value = isArray ? arrayValue : regularValue;
case 'single_line_text':
case 'multi_line_text':
default:
value = answer.value;
break;
}
To add a new field type, add another case statement above the ‘default’ case, formatted like the below:
case 'fieldtype':
// functions go here
value = "value you want to return"
break;
To update a specific field, you will navigate to the bottom of the file and uncomment the section. Below is an example of specific formatting for the mainPhone
field:
switch (answer.fieldApiName) {
case 'mainPhone': // The Field API name
if (isArray) {
arrayValue = answer.value.map((value) => ({
url: Formatter.phoneLink({mainPhone: value}),
label: Formatter.nationalizedPhoneDisplay({mainPhone: value})
}
));
} else {
regularValue = {
url: Formatter.phoneLink({mainPhone: answer.value}),
label: Formatter.nationalizedPhoneDisplay({mainPhone: answer.value})
};
}
value = isArray ? arrayValue : regularValue;
break;
}