Share Your Work

piersg
Key Advisor

Autocomplete JS for searching through a HubDB

SOLVE

I wrote this to have autocomplete on search for a HubDB.

 

Suggestions/feedback welcome!

 

HTML:

 

<form class="form" name="your-form" class="search-form" id="autocomplete-container" autocomplete="off">
  <input type="text" class="search-input" name="title" aria-label="Search" placeholder="Search" id="autocomplete-input">
  <ul id="autocomplete-results">
  </ul>
  <input class="hide" type="submit" name="submit">
</form>

 

JS:

 

// 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.

 

CSS if you want it:

 

#automcomplete-container {
  position: relative;
}
#autocomplete-input {
  margin-bottom: 0;
}
#autocomplete-results {
	display: none;
  flex-direction: column;
	width: 100%;
	list-style: none;
	margin-left: 0;
	box-shadow: 0px 4px 7px -1px rgba(45,62,80,.2);
	position: absolute;
  background-color: white;
  z-index: 1;
}
#autocomplete-results a {
	width: 100%;
	padding: 10px 15px;
	color: black;
}
#autocomplete-results a:hover {
	background: #eee;
}

 

1 Accepted solution
piersg
Solution
Key Advisor

Autocomplete JS for searching through a HubDB

SOLVE

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

<script src="{{ get_asset_url('[autocomplete_js_file_path]') }}" id="autoScript" data-id="{{dynamic_page_hubdb_table_id}}"></script>

 

//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);

 

 

View solution in original post

6 Replies 6
piersg
Solution
Key Advisor

Autocomplete JS for searching through a HubDB

SOLVE

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

<script src="{{ get_asset_url('[autocomplete_js_file_path]') }}" id="autoScript" data-id="{{dynamic_page_hubdb_table_id}}"></script>

 

//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);

 

 

piersg
Key Advisor

Autocomplete JS for searching through a HubDB

SOLVE

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 = '';
    }
  }
}

 

Edit: small update again

OSaguibo
Member

Autocomplete JS for searching through a HubDB

SOLVE

@piersg, thank you for this. It helps us a lot.
 

Do you have sample code of this using HubDB API v3?

0 Upvotes
stefen
Key Advisor | Partner
Key Advisor | Partner

Autocomplete JS for searching through a HubDB

SOLVE

@piersg cool! Do you have a live demo by chance?

Stefen Phelps, Community Champion, Kelp Web Developer
0 Upvotes
piersg
Key Advisor

Autocomplete JS for searching through a HubDB

SOLVE

Thanks @stefen 🙂

 

Yeah sure: http://www-mews-com.sandbox.hs-sites.com/en/products/marketplace

 

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)

natsumimori
Community Manager
Community Manager

Autocomplete JS for searching through a HubDB

SOLVE

Hi @piersg , thanks for sharing!

@stefen , @Kevin-C , @Chris-M , @Jake_Lett , you guys might be interested in this:)

0 Upvotes