Skip to content

Instantly share code, notes, and snippets.

@mjy9088
Last active December 30, 2021 06:48
Show Gist options
  • Save mjy9088/ac3a033735d2d904cec08c76cebb6d33 to your computer and use it in GitHub Desktop.
Save mjy9088/ac3a033735d2d904cec08c76cebb6d33 to your computer and use it in GitHub Desktop.
Input autocompletion
var InputAutocomplete = window.InputAutocomplete = (function () {
function result(options) {
if (!(this instanceof result)) throw new Error('InputAutocomplete is a constructor');
if (!options) throw new Error('InputAutocomplete requires options to be provided');
this.inputElement = options.inputElement;
this.show = options.show;
this.hide = options.hide;
this.setFailed = options.failed;
this.setLoading = options.loading;
this.setPlaceholder = options.placeholder;
this.listElement = options.listElement;
this.insertBefore = options.insertBefore || null;
this.dataLoader = options.dataLoader;
this.renderer = options.renderer;
this.stopped = true;
this.changeListener = this.change.bind(this);
this.focusListener = this.focus.bind(this);
this.blurListener = this.blur.bind(this);
this.loading = false;
this.pending = false;
this.data = [];
this.elementPool = [];
this.showingElements = [];
}
Object.defineProperties(result.prototype, {
start: {
value: function () {
if (!this.stopped) return;
this.stopped = false;
this.inputElement.addEventListener('keyup', this.changeListener);
this.inputElement.addEventListener('focus', this.focusListener);
this.inputElement.addEventListener('blur', this.blurListener);
this.change();
if (this.inputElement == document.activeElement) {
this.show();
} else {
this.hide();
}
}
},
stop: {
value: function () {
if (this.stopped) return;
this.stopped = true;
this.inputElement.removeEventListener('keyup', this.changeListener);
this.inputElement.removeEventListener('focus', this.focusListener);
this.inputElement.removeEventListener('blur', this.blurListener);
this.hide();
}
},
change: {
value: function () {
if (this.loading) {
this.pending = true;
return;
} else {
this.loading = true;
this.setLoading(true);
}
var self = this;
this.dataLoader(this.inputElement.value, function (data) {
self.loading = false;
if (self.pending) {
self.change();
} else {
self.setLoading(false);
}
self.pending = false;
if (!data) {
self.setFailed(true);
return;
}
self.setFailed(false);
self.data = data;
self.refresh();
});
}
},
refresh: {
value: function () {
this.setPlaceholder(this.data.length == 0);
while (this.showingElements[this.data.length]) {
var element = this.showingElements.pop();
this.listElement.removeChild(element);
this.releaseElement(element);
}
for (var i = 0; i < this.data.length; i++) {
if (this.showingElements[i]) {
this.renderer(this.data[i], this.showingElements[i]);
} else {
this.showingElements[i] = this.acquireElement(this.data[i]);
this.listElement.insertBefore(this.showingElements[i], this.insertBefore);
}
}
}
},
focus: {
value: function () {
this.show();
}
},
blur: {
value: function () {
this.hide();
}
},
acquireElement: {
value: function (data) {
return this.elementPool.length ? this.renderer(data, this.elementPool.pop()) : this.renderer(data);
}
},
releaseElement: {
value: function (element) {
this.elementPool.push(element);
}
}
});
return result;
})();
window.addEventListener('DOMContentLoaded', function () {
function dataLoader(text, callback) {
var length = Math.max(Math.floor(Math.pow(Math.abs(text.split('').reduce(function (acc, curr) {
var result = (acc << 5) - acc + curr.charCodeAt(0);
return result & result;
}, 0)), 0.123)) - 5, 0);
setTimeout(function () {
callback(Array.from(new Array(length)).map(function (_, i) {
return {
name: text + (i ? ' ' + (i + 1) : ''),
id: i
};
}));
}, 250);
}
function renderer(data, recycle) {
var result = recycle;
if (!result) {
result = document.createElement('li');
}
result.innerText = data.name;
return result;
}
var input = document.getElementById('input-autocomplete-input');
var toggle = document.getElementById('input-autocomplete-toggle');
var list = document.getElementById('input-autocomplete-list');
var loading = document.getElementById('input-autocomplete-loading');
var failed = document.getElementById('input-autocomplete-failed');
var placeholder = document.getElementById('input-autocomplete-placeholder');
list.addEventListener('click', function(e) {
if (e.target.parentElement == list) {
input.value = e.target.innerText;
}
});
new InputAutocomplete({
inputElement: input,
listElement: list,
show: function () { toggle.style.display = 'block'; },
hide: function () { setTimeout(function () { toggle.style.display = ''; }, 333); },
loading: function (b) { loading.style.display = b ? 'block' : ''; },
failed: function (b) { failed.style.display = b ? 'block' : ''; },
placeholder: function (b) { placeholder.style.display = b ? 'block' : ''; },
dataLoader: dataLoader,
renderer: renderer
}).start();
});
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
border: none;
box-sizing: border-box;
}
header, footer {
height: 720px;
background-color: #444444;
}
.test {
padding: 24px;
}
#input-autocomplete-input {
box-sizing: border-box;
width: 100%;
height: 36px;
line-height: 24px;
padding: 5px;
border: 1px solid #000000;
border-radius: 6px;
}
#input-autocomplete-toggle {
display: none;
position: relative;
z-index: 1;
height: 0;
overflow: visible;
}
#input-autocomplete-list {
box-sizing: border-box;
width: 100%;
background: #FFFFFF;
margin: 0;
padding: 5px;
border: 1px solid #000000;
border-radius: 6px;
}
#input-autocomplete-list li {
list-style: none;
box-sizing: border-box;
width: 100%;
padding: 0 6px;
cursor: pointer;
line-height: 24px;
/**/
min-height: 24px;
/*/
height: 24px;
white-space: pre;
text-overflow: ellipsis;
/**/
}
#input-autocomplete-list li:hover {
background-color: #FFFFBB;
}
#input-autocomplete-list #input-autocomplete-loading,
#input-autocomplete-list #input-autocomplete-failed,
#input-autocomplete-list #input-autocomplete-placeholder {
display: none;
cursor: default;
background-color: #DDDDDD;
}
<!DOCTYPE html>
<html>
<head>
<title>Input Autocomplete Test</title>
<link rel="stylesheet" href="./style.css" />
<script src="./script.js"></script>
<script src="./input-autocomplete.js"></script>
</head>
<body>
<header>
HEADER
</header>
<main>
MAIN
<section class="test">
<input type="text" id="input-autocomplete-input" autocomplete="off" />
<div id="input-autocomplete-toggle">
<ul id="input-autocomplete-list">
<li id="input-autocomplete-loading">Loading...</li>
<li id="input-autocomplete-failed">Failed to load</li>
<li id="input-autocomplete-placeholder">No data matching the text</li>
</ul>
</div>
</section>
</main>
<footer>
FOOTER
</footer>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment