algolia / algoliasearch-zendesk

Integrate Algolia within your Zendesk Help Center in minutes.
https://community.algolia.com/zendesk
Other
21 stars 12 forks source link

Hook into url for autocomplete _onSelected function #125

Closed nicholaskillin closed 3 years ago

nicholaskillin commented 3 years ago

Hey,

So we are using this integration with Zendesk and love it. However, we have run into a bit of a snag with one of our subdomains.

We have several subdomains for different products, with each of them using this integration as is.

We also have one subdomain that is the main landing page for our support. We want to build out a search here that searching all of our articles across all of our indices.

We have already created the main index that houses all of those articles and you can search all of the articles from our main support page.

However, when you select an article, the URL that is passed to the window location is a relative link. Since the articles don't actually live in this subdomain, that is causing things to break.

We would love it if we had the option to pass in a custom URL formula that is used in the _onSelected function of the AutoComplete class. Any change this is something you would consider?

Jerska commented 3 years ago

Hi @nicholaskillin .

First, thanks for the great feedback.

With the naming you chose for your index zendesk_all_articles, most of the functionality should work as expected because we mostly use the subdomain param to compute the index name (we also use it for the powered by link, that you can remove with poweredBy: false if you're a paying customer).

Just a few notes for future readers that would like to do the same:

Then you'll hit the main roadblock: clicking on a result will not redirect to the correct location. Indeed, as you can see, both our autocomplete & instantsearch will use an absolute URL without the domain:

The reason for that is a cosmetic one: when a user hovers over the link in instantsearch, we don't want to display https://algolia-support.zendesk.com/hc/en-us/articles/123, but rather https://support.algolia.com/hc/en-us/articles/123.

Autocomplete

To override the behavior on selection, you have the possibility to override the autocomplete:selected event of the autocomplete. To do so, you can first store the result of algoliasearchZendeskHC in a variable. This stores a variable for the Autocomplete instance, which itself stores all the autocomplete.js instances. Modifying this is considered as a hack, and you shouldn't expect future updates' code to be compatible with this, you should hence pin the library version as described here.

var zendeskSearch = algoliasearchZendeskHC(/* ... */);

var autocompletes = zendeskSearch.search.autocompletes || [];
for (let i = 0; i < autocompletes.length; ++i) {
  var autocomplete = autocompletes[i];
  autocomplete.off('autocomplete:selected'); // Remove default handler
  autocomplete.on('autocomplete:selected', function (event, suggestion, dataset) {
    console.log(event, suggestion, dataset);
    // Your custom redirect logic here
  });
}

EDIT: This is not enough, see answer below for a complete solution for the autocomplete.

Instantsearch

For instantsearch, it will be easier to handle. As you can see from the link before, the links there are handled directly as <a> tags in the instantsearch.hit template. You can read how to override a template here: https://community.algolia.com/zendesk/documentation/#modifying-templates .

Assuming you've put each record's subdomain in a subdomain attribute, you could modify it to have the complete URL with something like this:

algoliasearchZendeskHC({
  appId: 'xxxx',
  // ...
  templates: {
    instantsearch: {
      hit: algoliasearchZendeskHC.compile(`
        <div
          class="search-result"
        >
          ...
            <a class="search-result-link" href="https://[[subdomain]].zendesk.com[[ baseUrl ]][[ locale.locale ]]/articles/[[ id ]]">
              [[& _highlightResult.title.value ]]
            </a>
          ...
        </div>
      `)
    }
  }
});
nicholaskillin commented 3 years ago

Hi @Jerska,

Thank you for the detailed answer! That all makes sense to me in theory. However, I added all of that to my document_head.hbs file in Zendesk and it didn't seem to change anything. I suspect this is because that for loop is running when the page first loads, and at that time, there are no autocompletes. I tried adding a simple console.log to the for loop and nothing ever gets logged after searching. Here is what I have:

<script type="text/javascript">
  var zendeskSearch = algoliasearchZendeskHC({
    applicationId: [applicationId],
    apiKey: [apiKey],
    subdomain: 'all',
    debug: true,
  });

  var autocompletes = zendeskSearch.search.autocompletes || [];
  for (let i = 0; i < autocompletes.length; ++i) {
    var autocomplete = autocompletes[i];
    console.log(autocomplete)
    autocomplete.off('autocomplete:selected'); // Remove default handler
    autocomplete.on('autocomplete:selected', function (event, suggestion, dataset) {
      console.log(event, suggestion, dataset);
      // Your custom redirect logic here
    });
  };
</script>

I have a feeling I just have this code in the wrong place. Any advice for how to get this loop working?

Update

So it turns out if I console.log(zendeskSearch.search) from inside a $(document).ready() function I can see that autocompletes is an array with 3 objects in it.

However, if I console.log(zendeskSearch.search.autocompletes) from the same function I just get an empty array.

Jerska commented 3 years ago

That would indeed make sense, as this is only populated when render is called, which is only called instantly if the DOM is already ready or on DOM ready when it's not yet the case. I tried to run this code on an already loaded page from my console and didn't think twice about whether or not this would work right after the invocation.

There is one little thing we need to do to also handle future calls, as you properly suggested in your initial message: overriding Autocomplete._onSelected. Fortunately, JavaScript is a language which allows us to modify an object methods, so it could be done like so:

    var zendeskSearch = algoliasearchZendeskHC(/* ... */);

    function handleSelected(event, suggestion, dataset) {
      console.log(event, suggestion, dataset);
      // Your custom redirect logic here
    }

    // Handle future `render` invocation
    if (zendeskSeach.search.__proto__._onSelected) {
      zendeskSearch.search.__proto__._onSelected = function () {
        return handleSelected;
      }
    }

    // Handle already called `render`
    var autocompletes = zendeskSearch.search.autocompletes || [];
    for (let i = 0; i < autocompletes.length; ++i) {
      var autocomplete = autocompletes[i];
      autocomplete.off('autocomplete:selected'); // Remove default handler
      autocomplete.on('autocomplete:selected', handleSelected);
    }

As long as you keep your code in the document_head.hbs template, it should always only rely on the "Handle future render invocation" code path, but my guess is that it's safer to have both.

nicholaskillin commented 3 years ago

That worked great. Thank you so much!

Jerska commented 3 years ago

Thanks for keeping us updated! I'm glad it did!

nicholaskillin commented 3 years ago

Just as an update to this, turns out this did not fully fix the integration for us. If someone searches and hits return instead of selecting an article, then they are taken to a new page that displays the results.

InstantSearch has an onClick handler that has the same behavior as onSelected, so we had to override onClick as well for that page.

For anyone reading in the future, to fix this we just added this to the above solution.

if (zendeskSeach.search.__proto__._onClick) {
  zendeskSearch.search.__proto__._onClick = function () {
    return handleSelected;
  }
}
Jerska commented 3 years ago

Thanks for the follow-up @nicholaskillin . I'm suprised though, nowhere do we define this function in the sources.

Also, in the code you provide, I would have expected handleSelected() not handleSelected.

For me, the solution in the instantsearch page is to override the hit template, which includes the link to the article. (See my first message, "Instantsearch" section)

nicholaskillin commented 3 years ago

🤦 Oops. I wrote onClick but overrode trackClick which has a totally different purpose. I'll try overriding the template instead. Thanks!