Skip to content

Instantly share code, notes, and snippets.

@joona
Last active October 31, 2024 09:27
Show Gist options
  • Save joona/ced7764041408d0b86626f381878fa12 to your computer and use it in GitHub Desktop.
Save joona/ced7764041408d0b86626f381878fa12 to your computer and use it in GitHub Desktop.
export function dispatch(el, eventName, payload) {
el || (el = document);
el = el.el || el;
var event = new CustomEvent(eventName);
event.details = payload || {};
el.dispatchEvent(event);
}
export function listen(el, eventName, callback) {
el || (el = document);
el = el.el || el;
el.addEventListener(eventName, callback);
return callback; // return callback reference for unbinding
}
import { el, text, list, setChildren } from 'redom';
import { dispatch, listen } from '../lib/custom_events';
const field = el.extend('.field');
export class AutoCompleteSuggestion {
constructor(options) {
this.el = el('.autocomplete-suggestion', [
this.title = el('span', { onclick: this.onClick.bind(this) })
]);
}
onClick(e) {
dispatch(this.el.parentNode.parentNode, 'autocomplete:select', {
value: this.value
});
}
update(data) {
this.value = data.title;
this.el.classList.remove('focused');
if(data.focused) {
this.el.classList.add('focused');
}
if(data.matcher) {
var idx = data.title.indexOf(data.matcher);
var len = data.matcher.length;
setChildren(this.title, [
text(data.title.substr(0, idx)),
el('strong', data.matcher),
text(data.title.substr(idx+len, data.title.length))
]);
} else {
this.title.textContent = data.title;
}
}
}
export class AutoCompleteField {
constructor(options) {
options || (options = {});
const label = options.label;
options.type || (options.type = 'text');
options.autocomplete = false;
delete options.label;
options.onkeyup = this.onKeyUp.bind(this);
options.onkeydown = this.onKeyDown.bind(this);
this.el = el('.field.autocomplete', [
this.label = el('label', label),
this.input = this.inputField || el('input', options),
this.autocomplete = list('.autocomplete', AutoCompleteSuggestion, 'title')
]);
this.list = [];
this.focused = 0;
this.autocomplete.el.classList.add('hidden');
listen(this.el, 'autocomplete:select', e => {
this.onSuggestionSelect(e.details.value);
});
}
onKeyDown(e) {
if([40, 38].indexOf(e.keyCode) > -1) {
var rval = this.onArrowKey(e);
this.updateVisibleSuggestions(this.getMatchingListItems(this.input.value), this.input.value);
return rval;
}
if(e.keyCode == 13) {
return this.onEnter(e);
}
}
onKeyUp(e) {
if([40, 38, 13].indexOf(e.keyCode) > -1) return;
this.focused = 0;
this.updateVisibleSuggestions(this.getMatchingListItems(this.input.value), this.input.value);
}
onEnter(e) {
e.preventDefault();
e.stopPropagation();
var matches = this.getMatchingListItems(this.input.value);
var value = matches[this.focused];
this.onSuggestionSelect(value);
return false;
}
onArrowKey(e) {
e.stopPropagation();
e.preventDefault();
var keyCode = e.keyCode;
var matches = this.getMatchingListItems(this.input.value).length - 1;
if(keyCode == 40) {
if(this.focused < matches) this.focused++;
} else if(keyCode == 38) {
if(this.focused > 0) this.focused--;
}
return false;
}
onSuggestionSelect(value) {
this.input.value = value;
this.hideAutocomplete();
}
getMatchingListItems(query) {
if(!query) return [];
var matches = this.list.filter(x => {
return x.indexOf(query) > -1;
});
return matches;
}
hideAutocomplete() {
this.autocomplete.el.classList.add('hidden');
}
showAutocomplete() {
this.autocomplete.el.classList.remove('hidden');
}
checkShowAutocomplete(matches) {
if(matches.length > 0) {
this.showAutocomplete();
} else {
this.hideAutocomplete();
}
}
getMatchingString() {
var val = this.input.value;
return val;
}
updateVisibleSuggestions(suggestions) {
var matcher = this.getMatchingString();
var matches = suggestions.map((x, i) => {
return {
title: x,
matcher: matcher,
focused: i == this.focused
};
});
this.checkShowAutocomplete(matches);
this.autocomplete.update(matches);
}
updateSuggestions(arr) {
this.list = arr;
}
update(data) {
if(data.suggestions) this.updateSuggestions(arr);
}
}
export class MultipleAutoCompleteField extends AutoCompleteField {
constructor(options) {
this.separator = options.separator || ',';
delete options.separator;
super(options);
}
onSuggestionSelect(value) {
var values = this.input.value.split(this.separator).map(x => {
return x.trim();
});
values.pop();
values.push(value);
return super.onSuggestionSelect(values.join(this.separator));
}
getQuery() {
var parts = this.input.value.split(this.separator);
var last = parts.pop().trim();
return last;
}
getMatchingListItems(query) {
return super.getMatchingListItems(this.getQuery());
}
getMatchingString() {
return this.getQuery();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment