Adding KB search to your website with AJAX
The Zendesk help center provides users with search boxes to find articles in the knowledge base. You might still want users to search the KB from your main website or web application without having to jump out to the help center site. This article describes how to let users search your knowledge base from your website using standard JavaScript and AJAX.
You don't need a server-side component to add this functionality to your site. The search endpoints in the Help Center API implement Cross-Origin Resource Sharing (CORS). CORS lets any modern browser access resources in other domains, getting around the browsers' same-origin policy restriction. The search endpoints can be accessed by any domain in a cross-site manner. To learn more about CORS, see Using CORS on the HTML5 Rocks site.
Note: For security reasons, the only Zendesk API enpdoints that can be accessed directly from a browser are those that don't require sending authentication credentials with requests. This limits cross-domain requests to the small number of APIs that are allowed for anonymous users. This includes the help center search endpoints. For others, see the "Allowed For" section of the API docs.
Another option for integrating KB search on your website is to add the Zendesk Support Web Widget to your site's pages. See Using Web Widget to embed customer service in your website.
Disclaimer: Zendesk provides this article for instructional purposes only. Zendesk does not support or guarantee the code. Zendesk also can't provide support for third-party technologies such as JavaScript and jQuery.
Creating the form
This isn't a web design tutorial so we'll keep it simple. You can modify the look and feel of the form to your liking later.
The basic HTML follows. You can insert the body content in a div tag or section element in existing pages. The form contains one text box and one Search button.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="zendesk.css">
</head>
<body>
<h3>Search the knowledge base</h3>
<form id="searchForm" method="post">
<input type="text" id="query" name="query"><br>
<button id="search_btn">Search</button>
</form>
<script src="zendesk.js"></script>
</body>
</html>
The HTML links to a simple CSS file named zendesk.css, which contains the following rules:
body {
font-family: Arial, Helvetica, sans-serif;
}
#search_results ul {
font-size: 10pt;
list-style-type: none;
padding-top: 10px;
padding-left: 0;
margin-left: 0;
}
The script
tag just before the closing body
tag imports the JavaScript in the zendesk.js file. The content of this file is covered in more details in the sections that follow.
Making the API request
The first order of business of the zendesk.js file is to add an event listener to listen for the Search button click event. An event listener runs a function when an event is detected. The following event listener gets a reference by id to the button element in your form and runs a function named search_kb when a user clicks it:
document.getElementById('search_btn').addEventListener('click', search_kb, false);
The search_kb function can be defined as follows:
function search_kb(event) {
event.preventDefault();
var search_string = encodeURIComponent(document.getElementById('query').value);
var url = 'https://your_subdomain.zendesk.com/api/v2/help_center/articles/search.json?query=' + search_string;
var my_request = new XMLHttpRequest();
my_request.open('get', url, true);
my_request.onload = show_results;
my_request.onerror = show_error;
my_request.send();
}
The function is explained in more detail in the next section. Replace your_subdomain in the url with your Zendesk Support subdomain.
How it works
First off, the script prevents the HTML form's default submit action when the user clicks the button:
function search_kb(event) {
event.preventDefault();
...
The next few lines build the url where the script will ask for search results. The Search API doc specifies the following URL:
GET /api/v2/help_center/articles/search.json?query={search_string}
Because the search string is sent as a URL parameter, it needs to be url-encoded and appended to the API url:
var search_string = encodeURIComponent(document.getElementById('query').value);
var url = 'https://your_subdomain.zendesk.com/api/v2/help_center/articles/search.json?query=' + search_string;
...
The script gets the value of the form's text box by id and url-encodes it using the encodeURIComponent()
method in JavaScript.
Next, the script creates and configures a new AJAX object (also called an "XHR object"), a specialized JavaScript object for making HTTP requests:
var my_request = new XMLHttpRequest();
my_request.open('get', url, true);
...
The AJAX object is configured to make a GET request to the resource specified by the url variable.
The script then specifies functions to handle the outcome of the request, good or bad:
my_request.onload = show_results;
my_request.onerror = show_error;
...
The AJAX object's onload
and onerror
properties are function objects. The first runs when the request is successfully completed. The second runs when the request fails because of an error. The functions are defined separately later.
Finally, the script makes the HTTP request:
my_request.send();
Handling request errors
Things go wrong. The API could be down for maintenance or the user could be experiencing network connectivity issues. If there's a problem with the request, then the search_kb() function runs the following show_error function to display an alert:
function show_error() {
alert('Request error');
}
Displaying the results
If the request was successfully completed (if my_request.onload
), then the search_kb function runs the show_results function. The function has 3 jobs:
- Insert new HTML elements in the page to display the results
- Extract the results from the HTTP response and add them to the newly created elements
- Clear any results from the previous search, if any, before displaying the new results
The function can be defined as follows:
function show_results() {
clearPreviousResults();
var form = document.getElementById('searchForm');
var resultDiv = document.createElement('div');
resultDiv.id = 'search_results';
document.body.insertBefore(resultDiv, form.nextSibling);
var data = JSON.parse(this.responseText);
if (data.results.length) { // if results exist
var resultList = document.createElement('ul');
resultDiv.appendChild(resultList);
for (var i = 0; i < data.results.length; ++i) {
var article = data.results[i];
var resultItem = document.createElement('li');
resultList.appendChild(resultItem);
resultItem.innerHTML = '<a href="' + article_url + '" target="_blank">' + article.title + '</a>';
}
} else { // no results found
var result = document.createElement('p');
result.innerHTML = 'No results';
resultDiv.appendChild(result);
}
}
How it works
The script starts off by clearing any previous results on the page. The clearPreviousResults()
function is defined later.
Next, the script creates a div tag with an id of search_results and inserts it after the form:
var form = document.getElementById('searchForm');
var resultDiv = document.createElement('div');
resultDiv.id = 'search_results';
document.body.insertBefore(resultDiv, form.nextSibling);
...
The reference to the form element is necessary so that the div tag can be inserted after it using form.nextSibling
.
Next, the script extracts the data from the HTTP response and converts it from JSON to a JavaScript object:
var data = JSON.parse(this.responseText);
...
Because the show_results function is member of the my_request AJAX object, you can access any of the object's properties with the this
syntax, as in this.responseText
. You don't need to pass the AJAX object to the function.
The script can now check to see if the search returned results. If it did, the script creates an unordered list and appends it to the div tag, which can still be referenced with the resultDiv variable:
if (data.results.length) { // if results exist
var resultList = document.createElement('ul');
resultDiv.appendChild(resultList);
...
Next, the script iterates through each article in the result set, creates an list element for each article, appends it to the unordered list, and inserts a linked article title to each list item:
for (var i = 0; i < data.results.length; ++i) {
var article = data.results[i];
var resultItem = document.createElement('li');
resultList.appendChild(resultItem);
resultItem.innerHTML = '<a href="' + article_url + '" target="_blank">' + article.title + '</a>';
}
...
Otherwise, if no results are returned, the script creates a paragraph with the message "No results", and appends it to the div tag:
} else { // no results found
var result = document.createElement('p');
result.innerHTML = 'No results';
resultDiv.appendChild(result);
}
On subsequent searches, the previous results are cleared with the clearPreviousResults()
function, which can be coded as follows:
function clearPreviousResults(){
var results = this.document.getElementById('search_results');
if (results) {
results.remove();
}
}
The script gets a reference by id to the div tag containing the results. If the div tag exists -- if the script created it previously -- the script removes it and all its children from the DOM so that it can create a fresh one.
Code complete
document.getElementById('search_btn').addEventListener('click', search_kb, false);
function search_kb(event) {
event.preventDefault();
var search_string = encodeURIComponent(document.getElementById('query').value);
var url = 'https://your_subdomain.zendesk.com/api/v2/help_center/articles/search.json?query=' + search_string;
var my_request = new XMLHttpRequest();
my_request.open('get', url, true);
my_request.onload = show_results;
my_request.onerror = show_error;
my_request.send();
}
function show_error() {
alert('Request error');
}
function show_results() {
clearPreviousResults();
var form = document.getElementById('searchForm');
var resultDiv = document.createElement('div');
resultDiv.id = 'search_results';
document.body.insertBefore(resultDiv, form.nextSibling);
var data = JSON.parse(this.responseText);
if (data.results.length) { // if results exist
var resultList = document.createElement('ul');
resultDiv.appendChild(resultList);
for (var i = 0; i < data.results.length; ++i) {
var article = data.results[i];
var resultItem = document.createElement('li');
resultList.appendChild(resultItem);
resultItem.innerHTML = '<a href="' + article.html_url + '" target="_blank">' + article.title + '</a>';
}
} else { // no results
var result = document.createElement('p');
result.innerHTML = 'No results';
resultDiv.appendChild(result);
}
}
function clearPreviousResults(){
var results = this.document.getElementById('search_results');
if (results) {
results.remove();
}
}
Appendix: Using jQuery
You can use jQuery to add KB search to your website. The syntax is slightly different but the logic is the same. See the previous sections if something in the flow isn't clear.
Make sure to import the jQuery library into your HTML file. For example, you can add the following script
tag before the <script src="zendesk.js">
tag:
<script src="http://code.jquery.com/jquery-2.1.4.js"></script>
...
You can then rewrite the JavaScript in your zendesk.js file as follows:
$('#search_btn').click(function(event) {
event.preventDefault();
var search_string = encodeURIComponent($('#query').val());
$.ajax({
url: 'https://your_subdomain.zendesk.com/api/v2/help_center/articles/search.json?query=' + search_string,
contentType: 'application/json',
success: show_results,
error: show_error
});
});
function show_error() {
alert('Request error');
}
function show_results(data) {
clearPreviousResults();
$('<div id="search_results"></div>').insertAfter('#searchForm');
if (data.results.length) { // if results exist
$('<ul></ul>').appendTo('#search_results');
for (var i = 0; i < data.results.length; ++i) {
var article = data.results[i];
var a_href = '<a href="' + article.html_url + '" target="_blank">' + article.title + '</a>';
$('<li></li>').html(a_href).appendTo('ul');
}
} else {
$('<p>No results</p>').appendTo('#search_results');
}
}
function clearPreviousResults(){
var results = $('#search_results');
if (results) {
results.remove();
}
}