Skip to content

Instantly share code, notes, and snippets.

@staydecent
Created July 31, 2015 22:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save staydecent/35a45924dbdc3baf60b3 to your computer and use it in GitHub Desktop.
Save staydecent/35a45924dbdc3baf60b3 to your computer and use it in GitHub Desktop.
Native DOM AutoCompleter
function AutoCompltr(wrapper, suggestions, inputProps, cb, oninput) {
this.wrapperNode = wrapper;
this.suggestionsList = [];
this.suggestionNodes = [];
this.pointer = -1;
this.callback = cb;
this.suggestionsNode = document.createElement('ul');
this.inputNode = document.createElement('input');
if (suggestions && suggestions.length) {
this.suggestionsList = suggestions;
}
if (inputProps) {
for (var prop in inputProps) {
this.inputNode[prop] = inputProps[prop];
}
}
this.wrapperNode.className += " AutoCompltr";
this.suggestionsNode.className = "AutoCompltr-suggestionList";
this.inputNode.type = "text";
this.inputNode.className = "AutoCompltr-input";
this.wrapperNode.appendChild(this.inputNode);
this.wrapperNode.appendChild(this.suggestionsNode);
if (oninput) {
this.inputNode.addEventListener('input', oninput.bind(this));
}
this.bindEventHandlers();
}
AutoCompltr.prototype.bindEventHandlers = function() {
// Input
this.inputNode.addEventListener('keyup', function(e) {
if (['Up', 'Down', 'Enter'].indexOf(e.keyIdentifier) > -1) {
e.preventDefault();
e.stopPropagation();
var max = this.suggestionNodes.length - 1;
var current = this.pointer;
switch (e.keyIdentifier) {
case "Up":
if (current > 0)
D.removeClass(this.suggestionNodes[this.pointer], 'focus');
this.pointer = current - 1;
D.addClass(this.suggestionNodes[this.pointer], 'focus');
break;
case "Down":
if (current < max)
D.removeClass(this.suggestionNodes[this.pointer], 'focus');
this.pointer = current + 1;
D.addClass(this.suggestionNodes[this.pointer], 'focus');
break;
case "Enter":
if (this.pointer > max)
this.pointer = max;
this.handleSelection();
break;
}
} else {
if (this.inputNode.value !== "") {
this.displayOptions();
} else {
this.hideOptions();
}
}
}.bind(this));
// Input blur
this.inputNode.addEventListener('blur', function(e) {
this.hideOptions();
}.bind(this));
// Click to select
this.suggestionsNode.addEventListener('mousedown', function(e) {
this.pointer = D.getChildIndex(e.target);
this.handleSelection();
}.bind(this));
// Hide on off target click
document.body.addEventListener('click', function(e) {
this.hideOptions();
}.bind(this));
};
AutoCompltr.prototype.displayOptions = function() {
if (!this.suggestionsList.length) {
this.suggestionsNode.style.display = 'none';
this.pointer = -1;
return;
}
this.suggestionsNode.style.display = 'block';
this.pointer = -1;
var inputText = this.inputNode.value.toLowerCase();
var simpleSearch = function(suggestion) {
return (suggestion.toLowerCase().indexOf(inputText) !== -1);
};
var createOptionNode = function(suggestion, idx) {
var cls = ['AutoCompltr-suggestion'];
return [
'<li class="' + cls.join(' ') + '">',
suggestion,
'</li>'
].join('');
};
var htmlStr = this.suggestionsList
// .filter(simpleSearch)
.map(createOptionNode)
.join('');
this.suggestionsNode.innerHTML = htmlStr;
this.suggestionNodes = document.getElementsByClassName('AutoCompltr-suggestion');
// Need bind event to created nodes
// @TODO: performance? do we need to remove this listeners?
for (var x = 0; x < this.suggestionNodes.length; x++) {
this.suggestionNodes[x].addEventListener('mouseenter', onMouseEnter);
this.suggestionNodes[x].addEventListener('mouseleave', onMouseLeave);
}
var ctx = this;
function onMouseEnter(e) {
ctx.pointer = D.getChildIndex(e.target);
D.addClass(e.target, 'focus');
}
function onMouseLeave(e) {
D.removeClass(e.target, 'focus');
}
};
AutoCompltr.prototype.handleSelection = function() {
if (this.pointer !== -1)
this.inputNode.value = this.suggestionNodes[this.pointer].textContent;
if (this.callback)
this.callback(this.inputNode.value, this.pointer);
this.pointer = -1;
this.hideOptions();
};
AutoCompltr.prototype.hideOptions = function() {
this.suggestionsNode.style.display = 'none';
};
AutoCompltr.prototype.setSuggestions = function(suggestions) {
// instead of complex checks to see if 'suggestions' is an array-like
// object (even an empty one) let's just force an empty array.
if (!suggestions || !suggestions.length) {
suggestions = [];
}
this.suggestionsList = suggestions;
this.displayOptions();
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment