// variables
var input = document.getElementById('autocomplete-input');
// this will be the list of names to compare against user input
var list = [];
// this will be an object array that will have the path (if you want it, remove if you just want names)
var namePathArr = [];
//Ajax call to HubDB
var request = new XMLHttpRequest();
request.open('GET', "https://api.hubapi.com/hubdb/api/v2/tables/[DB ID]/rows?portalId=[PORTAL ID]", true);
request.onload = function() {
if (this.status >= 200 && this.status < 400) {
// Success!
var data = JSON.parse(this.response);
var tableArr = data.objects;
tableArr.forEach(function(obj) {
if(obj.path !== null && obj.path !== '') {
var name = obj.values['2'];
// remove path and object variables if you just want names
var path = obj.path;
var object = {
"name" : name,
"path" : path
};
// push to names array
list.push(name);
// push name and path to namePathArr (remove if you want)
namePathArr.push(object);
}
});
} else {
// We reached our target server, but it returned an error
console.log(this.response);
}
};
request.onerror = function(status, error) {
console.log(error);
};
request.send();
// autocomplete function to compare value against the list and return an array of matches
function autocomplete(val) {
var autocomplete_return = [];
for (i = 0; i < list.length; i++) {
if (val === list[i].toLowerCase().slice(0, val.length)) {
autocomplete_return.push(list[i]);
}
}
return autocomplete_return;
}
// input event to give value for the autocomplete match function
input.onkeyup = function(e) {
input_val = this.value; // updates the variable on each ocurrence
input_val = input_val.toLowerCase();
if (input_val.length > 0) {
var suggestions = [];
autocomplete_results = document.getElementById("autocomplete-results");
autocomplete_results.innerHTML = '';
suggestions = autocomplete(input_val);
//get current url without query or hash in path
var url = window.location.href.split('?')[0];
url = url.split('#')[0];
for (i = 0; i < suggestions.length; i++) {
// additional step to look up the suggestion in namePathArr to get the path, again remove this and the href if you just want names
let name = namePathArr.find(o => o.name === suggestions[i]);
// adds the suggestions as links with the href to the autocomplete results list
autocomplete_results.innerHTML += '<a href="'+url+'/'+name.path+'"><li>' + suggestions[i] + '</li></a>';
}
autocomplete_results.style.display = 'flex';
} else {
suggestions = [];
autocomplete_results.innerHTML = '';
}
}
You could probably get rid of the 'list' array and just use namePathArr to get both name and path, but I've had a long day haha.
I edited this slightly to make it more reusable . I added an id data attribute to the script tag, which has the HubL variable for the hubdb table id as a value
//add this
var dbID = document.getElementById("autoScript").getAttribute( "data-id" );
//make this a function
function getHubDB(id) {
var request = new XMLHttpRequest();
request.open('GET', "https://api.hubapi.com/hubdb/api/v2/tables/"+id+"/rows?portalId=5255565", true);
request.onload = function() {
if (this.status >= 200 && this.status < 400) {
// Success!
var data = JSON.parse(this.response);
var tableArr = data.objects;
tableArr.forEach(function(obj) {
if(obj.path !== null && obj.path !== '') {
var name = obj.values['2'];
if (name != undefined) {
var path = obj.path;
var object = {
"name" : name,
"path" : path
};
// push to names array
list.push(name);
// push name and path to namePathArr
namePathArr.push(object);
}
}
});
} else {
// We reached our target server, but it returned an error
console.log(this.response);
}
};
request.onerror = function(status, error) {
console.log(error);
};
request.send();
}
getHubDB(dbID);
I edited this slightly to make it more reusable . I added an id data attribute to the script tag, which has the HubL variable for the hubdb table id as a value
//add this
var dbID = document.getElementById("autoScript").getAttribute( "data-id" );
//make this a function
function getHubDB(id) {
var request = new XMLHttpRequest();
request.open('GET', "https://api.hubapi.com/hubdb/api/v2/tables/"+id+"/rows?portalId=5255565", true);
request.onload = function() {
if (this.status >= 200 && this.status < 400) {
// Success!
var data = JSON.parse(this.response);
var tableArr = data.objects;
tableArr.forEach(function(obj) {
if(obj.path !== null && obj.path !== '') {
var name = obj.values['2'];
if (name != undefined) {
var path = obj.path;
var object = {
"name" : name,
"path" : path
};
// push to names array
list.push(name);
// push name and path to namePathArr
namePathArr.push(object);
}
}
});
} else {
// We reached our target server, but it returned an error
console.log(this.response);
}
};
request.onerror = function(status, error) {
console.log(error);
};
request.send();
}
getHubDB(dbID);
Updated version, feel I might as well put it here. Haven't looked at this in a while so I'm sure it could be better/easier to read/more efficient (obviously some specific stuff in there for my use case e.g. item.values['33'] which you would have to refactor).
The script tag for DBs with child DBs:
<script src="{{ get_asset_url('[LINK TO AUTOCOMPLETE JS FILE]') }}" id="autoScript"{% if dynamic_page_route_level == 1 %}{% set table = hubdb_table_rows(dynamic_page_hubdb_table_id) %}{% endif %}{% for row in table %}{% if row.hs_path %} data-id-{{loop.index}}="{{row.hs_child_table_id}}"{% endif %}{% endfor %}></script>
Autocomplete.js
// variables
var input = document.getElementById('autocomplete-input');
input = input === null || input === undefined ? document.querySelectorAll('.autocomplete-input') : document.getElementById('autocomplete-input');
//get hubdb id from script data attribute
var container = document.getElementById("autoScript");
var dataId = container.getAttribute("data-id");
// this will be the list of names to compare against user input
var list = [];
// this will be an object array that will have the path (if you want it)
var namePathArr = [];
// this will be an array of unique categories for integrations
var categories = [];
// fetch function to get data from HubDB and push to list and namePathArr arrays
function getData(url1, url2) {
if (url1) { // if there are two urls (i.e. if there is a parent DB to get info from and child DBs)
var type;
fetch(url1)
.then(response => response.json()
.then(function(data) { // get the names of the child DBs from the parent so we can set as the type e.g. for resources, we need to know what type each resource is (e.g. podcast/event/research/webinar) so we can set the url accordingly
type = data.name;
type = type.split('db')[0];
return fetch(url2); // make a 2nd fetch request to the child DBs and return a promise
})
.then(response => response.json()
.then(function(data) {
let objArr = data.objects;
objArr.forEach(function(item) {
if(item.name !== undefined) {
item.type = type;
var object = {
"name" : item.name,
"type" : item.type
};
if (item.path === '' && item.type == 'webinars') {
object.path = item.values['33'];
object.external = true;
} else {
object.path = item.path;
}
// push to names array
list.push(item.name);
// push name and path to namePathArr
namePathArr.push(object);
}
});
})
))
.catch(error => console.log(error))
} else { // if there's just one DB, no child DBs, get the info from just that DB
fetch(url2)
.then(response => response.json()
.then(function(data) {
var objArr = data.objects;
objArr.forEach(function(obj) {
if(obj.path !== null && obj.path !== '' && obj.values['2'] !== undefined) {
var name = obj.values['2'];
var path = obj.path;
// get categories (for integrations)
var cat;
if (obj.values['5']['0'].name !== undefined || obj.values['5']['0'].name !== null) {
cat = obj.values['5']['0'].name.toLowerCase().replace(/\s/g, '-');
}
var object = {
"name" : name,
"path" : path,
};
// push to names array
list.push(name);
// push name and path to namePathArr
namePathArr.push(object);
// push unique categories to that categories array
if (cat !== undefined || cat !== null && categories.indexOf(cat) === -1) {
categories.push(cat)
}
}
});
})
)
.catch(error => console.log(error))
}
};
if (dataId === null) {
var dataAttr = container.dataset;
dataAttr = JSON.parse(JSON.stringify(dataAttr));
// for each HubDB id call the above fetch function, getting parent DB and child DBs
let db;
let dbRows;
Object.keys(dataAttr).forEach(function(key) {
db = `https://api.hubapi.com/hubdb/api/v2/tables/${dataAttr[key]}?portalId=[PORTAL ID]`;
dbRows = `https://api.hubapi.com/hubdb/api/v2/tables/${dataAttr[key]}/rows?portalId=[PORTAL ID]`;
});
getData(db, dbRows);
} else {
//getting info from just the one DB
let dbRows = `https://api.hubapi.com/hubdb/api/v2/tables/${dataId}/rows?portalId=[PORTAL ID]`;
getData(null, dbRows);
}
// autocomplete function to compare value against the list and return an array of matches
function autocomplete(val) {
const found = list.filter(x => x.toLowerCase().includes(val)).sort((a, b) => a.localeCompare(b));
return found;
}
// input event to give value for the autocomplete match function
// check if input is a NodeList i.e. if there are multiple autocomplete search bars on a page
if (NodeList.prototype.isPrototypeOf(input)) {
input.forEach(el => {
el.onkeyup = function(e) {
input_val = this.value; // updates the variable on each ocurrence
input_val = input_val.toLowerCase();
if (input_val.length > 0) {
var suggestions = [];
autocomplete_results = this.nextElementSibling;
autocomplete_results.innerHTML = '';
suggestions = autocomplete(input_val);
var url = window.location.href.split('?')[0];
url = url.split('#')[0];
var lastPartURL = url.substr(url.lastIndexOf('/') + 1);
if (lastPartURL !== 'marketplace') {
url = url.substr(0,url.lastIndexOf("/"));
}
for (i = 0; i < suggestions.length; i++) {
let name = namePathArr.find(o => o.name === suggestions[i]);
let res = suggestions[i].replace(new RegExp(input_val, "i"), '<span style="color: red;">$&</span>');
autocomplete_results.innerHTML += `<a href="${url}/${name.path}"><li>${res}</li></a>`;
}
autocomplete_results.style.display = 'flex';
} else {
suggestions = [];
autocomplete_results.innerHTML = '';
}
}
});
} else {
input.onkeyup = function(e) {
input_val = this.value; // updates the variable on each ocurrence
input_val = input_val.toLowerCase();
if (input_val.length > 0) {
var suggestions = [];
autocomplete_results = document.getElementById("autocomplete-results");
autocomplete_results.innerHTML = '';
suggestions = autocomplete(input_val);
var url = window.location.href.split('?')[0];
url = url.split('#')[0];
var lastPartURL = url.substr(url.lastIndexOf('/') + 1);
if (lastPartURL !== 'resources') {
url = url.substr(0,url.lastIndexOf("/"));
}
for (i = 0; i < suggestions.length; i++) {
let name = namePathArr.find(o => o.name === suggestions[i]);
let res = suggestions[i].replace(new RegExp(input_val, "i"), '<span class="text-primary">$&</span>');
if (name.external === true) {
autocomplete_results.innerHTML += `<a href="${name.path}" target="_blank"><li>${res}</li></a>`;
} else {
autocomplete_results.innerHTML += `<a href="${url}/${name.path}"><li>${res}</li></a>`;
}
}
autocomplete_results.style.display = 'flex';
} else {
suggestions = [];
autocomplete_results.innerHTML = '';
}
}
}
I'm still messing around with the HubDB HubL for this so some of the other functionality may be a bit broken but the autocomplete is fully functional (including clicking on a suggestion to go to the page)