Customize search results in Home

You can create a custom preview pane for search results that are displayed in Home using custom templates. You can define different responses for a search result depending on which fragment of the pane is clicked.

Similar to notification templates

Custom search result templates are similar to custom notification templates.

Parts of a template

The components of a template are called fragments. You create your template by including the fragments you need in a JSON object.

The set of fragments is similar to that for notification templates:

  • container groups other fragments, with no visible parts of its own

  • text displays text

  • list displays a list of name-value pairs

  • image displays an image

  • button (unique to search result templates) executes an action

Button fragment

The button fragment enables the user to do more than merely select a search result. The user can interact with the result in whatever way makes sense for your use case. For example, a View Client button would take a different action than a View Instrument button.

In Workspace v16.1 or later, you can also work with a splitButton fragment, which lets you include a button that's explicitly styled for a dropdown. For an example, see the API reference.

Actions

The button, splitButton, image, and text fragments process mouse clicks. Each of these fragments returns an optional action string, which you can use to direct your code. A click on an image could return the string ‘Zoom Image’, which could be used to display a full-size view of that image. A click on the text of a client’s name could return the string ‘Call Contact’, which could be used to start a phone call to that client.

Example action:

const myComposition: TemplateFragment = {
  ... // Fragment properties
  action: 'Call Contact';
}

// CLIProvider.onResultDispatch()
async function onResultDispatch(res: CLIDispatchedSearchResult) {
  if (res.action.name === 'Call Contact') {
    services.phone.call(res.data.phoneNumber);
  }
}

How it works

You create a JSON object to describe how to render your search results. That JSON object contains the fragments you need to display a search result, along with some style information for each fragment.

When the user selects a search result, the result data is rendered and displayed on the user's screen.

Home search result in custom template

How to do it

Leveraging custom templates for search results requires two phases:

  1. Define the custom template JSON object. This JSON object drives a large part of the custom search results.

  2. When generating search results, apply the template to the data for each search result.

Templates and fragments

The custom template can be thought of like a simplified HTML render, where the fragments are like simplified HTML elements.

FragmentCorresponds to
container<div>
text<span>
list<ul> with no list marker, and with name-value pairs
image<img>
button<button> but with more complex formatting possible
splitButton<button> with more complex formatting plus expandable menu that includes additional <button> elements

Three fragments have an optional children property; container, button, and splitButton. Just as HTML pages often have a <div> within a <div> within a <div>, custom templates can have a container within a container within a container. The children property enables this.

Because buttons often require flexible formatting, the button and splitButtons fragments can also contain children.

Styles

To continue the analogy with HTML, you can define styling for template fragments, via the style element. The style element can contain React inline style properties such as flex properties, colors, sizes, and fonts. Remember that React style properties use “camelCase” names, such as backgroundColor rather than “kebab-case” names, such as background-color, as in plain CSS.

📘

Do not use position:fixed

The position:fixed CSS property value is not allowed, to prevent elements from appearing outside their search result card; the create() function throws an error if it is used.

Optional fragments

Fragments can be optional. Why is this useful?

Not every fragment that you define is needed for every search result that the template containing it might be applied to. In some cases, the corresponding data might not exist, but the result of the search result is valid.

Data items related to a search result are mapped via the fragment's dataKey property. If a fragment's optional property is true and a value matching its dataKey property is not supplied for a search result, that fragment is not rendered. If optional is false or not defined, then missing data in a search result causes an error for that search result.

Put it together

Create the JSON object template. In this case, myComposition:

const myComposition: TemplateFragment = {
  type: 'container',
  style: {
    display: 'flex',
    flexFlow: 'column',
    // some more styling
  },
  children: [{
    type: 'text',
    style: {
      color: 'red',
      // ...
    }
    dataKey: 'description', // a key to templateData
  },
  {
    type: 'image',
    style: {
      width: '200px',
      // ...
    }
    dataKey: 'imageUrl',
  }]
}

This code creates a composition that looks like this:

Custom home template diagram

But there is a very important question that hasn’t been answered yet. How do you take the data you found in your search and put it on the rendered display?

You use the dataKey property of the fragment.

To illustrate, let’s say that, as in the template JSON object shown above, myComposition, you have a text fragment of red styled text where dataKey is set to description, and an image fragment where dataKey is set to imageUrl. Let’s further say your search results object also has properties named description and imageUrl.

And in your search result data you have a search result where the description property is set to the text “Company 1” and imageUrl points to an image file of the Company 1’s logo. For example, the first element of mySearchResults as seen here:

const mySearchResults: HomeSearchResult[] = [{
  ... // standard home search result options
  template: CLITemplate.Custom,
  templateContent: {
    layout: myComposition,
    data: {
      description: 'Company 1',
      imageUrl: 'https://company1.com/logo.png'
    }
  }
},
{
  ... // standard home search result options
  template: CLITemplate.Custom,
  templateContent: {
    layout: myComposition,
    data: {
      description: 'Company 2',
      imageUrl: 'https://company2.com/logo.png'
    }
  }
}];

When the search result is rendered by the custom template, the text 'Company 1' appears as red text in the rendered display, and Company 1’s logo appears below the red text. Likewise, the name and logo for Company 2 are displayed for the search result for Company 2.