372 lines
No EOL
17 KiB
JavaScript
372 lines
No EOL
17 KiB
JavaScript
var __assign = (this && this.__assign) || function () {
|
|
__assign = Object.assign || function(t) {
|
|
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
s = arguments[i];
|
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
t[p] = s[p];
|
|
}
|
|
return t;
|
|
};
|
|
return __assign.apply(this, arguments);
|
|
};
|
|
var TokenAutocomplete = /** @class */ (function () {
|
|
function TokenAutocomplete(options) {
|
|
this.KEY_BACKSPACE = 8;
|
|
this.KEY_ENTER = 13;
|
|
this.KEY_UP = 38;
|
|
this.KEY_DOWN = 40;
|
|
this.defaults = {
|
|
name: '',
|
|
selector: '',
|
|
noMatchesText: null,
|
|
initialTokens: null,
|
|
initialSuggestions: null,
|
|
suggestionsUri: '',
|
|
suggestionRenderer: TokenAutocomplete.Autocomplete.defaultRenderer,
|
|
minCharactersForSuggestion: 1
|
|
};
|
|
this.options = __assign(__assign({}, this.defaults), options);
|
|
var passedContainer = document.querySelector(this.options.selector);
|
|
if (!passedContainer) {
|
|
throw new Error('passed selector does not point to a DOM element.');
|
|
}
|
|
this.container = passedContainer;
|
|
this.container.classList.add('token-autocomplete-container');
|
|
if (!Array.isArray(this.options.initialTokens) && !Array.isArray(this.options.initialSuggestions)) {
|
|
this.parseTokensAndSuggestions();
|
|
}
|
|
this.hiddenSelect = document.createElement('select');
|
|
this.hiddenSelect.id = this.container.id + '-select';
|
|
this.hiddenSelect.name = this.options.name;
|
|
this.hiddenSelect.setAttribute('multiple', 'true');
|
|
this.hiddenSelect.style.display = 'none';
|
|
this.textInput = document.createElement('span');
|
|
this.textInput.id = this.container.id + '-input';
|
|
this.textInput.classList.add('token-autocomplete-input');
|
|
this.textInput.setAttribute('data-placeholder', 'enter some text');
|
|
this.textInput.contentEditable = 'true';
|
|
this.container.appendChild(this.textInput);
|
|
this.container.appendChild(this.hiddenSelect);
|
|
this.select = new TokenAutocomplete.MultiSelect(this);
|
|
this.autocomplete = new TokenAutocomplete.Autocomplete(this);
|
|
this.debug(false);
|
|
var me = this;
|
|
if (Array.isArray(this.options.initialTokens)) {
|
|
this.options.initialTokens.forEach(function (token) {
|
|
if (typeof token === 'object') {
|
|
me.select.addToken(token.value, token.text);
|
|
}
|
|
});
|
|
}
|
|
this.textInput.addEventListener('keydown', function (event) {
|
|
if (event.which == me.KEY_ENTER || event.keyCode == me.KEY_ENTER) {
|
|
event.preventDefault();
|
|
var highlightedSuggestion = me.autocomplete.suggestions.querySelector('.token-autocomplete-suggestion-highlighted');
|
|
if (highlightedSuggestion !== null) {
|
|
if (highlightedSuggestion.classList.contains('token-autocomplete-suggestion-active')) {
|
|
me.select.removeTokenWithText(highlightedSuggestion.textContent);
|
|
}
|
|
else {
|
|
me.select.addToken(highlightedSuggestion.getAttribute('data-value'), highlightedSuggestion.textContent);
|
|
}
|
|
}
|
|
else {
|
|
me.select.addToken(me.textInput.textContent, me.textInput.textContent);
|
|
}
|
|
me.clearCurrentInput();
|
|
}
|
|
else if (me.textInput.textContent === '' && (event.which == me.KEY_BACKSPACE || event.keyCode == me.KEY_BACKSPACE)) {
|
|
event.preventDefault();
|
|
me.select.removeLastToken();
|
|
}
|
|
});
|
|
this.textInput.addEventListener('keyup', function (event) {
|
|
var _a, _b;
|
|
if ((event.which == me.KEY_UP || event.keyCode == me.KEY_UP) && me.autocomplete.suggestions.childNodes.length > 0) {
|
|
var highlightedSuggestion = me.autocomplete.suggestions.querySelector('.token-autocomplete-suggestion-highlighted');
|
|
var aboveSuggestion = (_a = highlightedSuggestion) === null || _a === void 0 ? void 0 : _a.previousSibling;
|
|
if (aboveSuggestion != null) {
|
|
me.autocomplete.highlightSuggestion(aboveSuggestion);
|
|
}
|
|
return;
|
|
}
|
|
if ((event.which == me.KEY_DOWN || event.keyCode == me.KEY_DOWN) && me.autocomplete.suggestions.childNodes.length > 0) {
|
|
var highlightedSuggestion = me.autocomplete.suggestions.querySelector('.token-autocomplete-suggestion-highlighted');
|
|
var belowSuggestion = (_b = highlightedSuggestion) === null || _b === void 0 ? void 0 : _b.nextSibling;
|
|
if (belowSuggestion != null) {
|
|
me.autocomplete.highlightSuggestion(belowSuggestion);
|
|
}
|
|
return;
|
|
}
|
|
me.autocomplete.hideSuggestions();
|
|
me.autocomplete.clearSuggestions();
|
|
var value = me.textInput.textContent || '';
|
|
if (value.length >= me.options.minCharactersForSuggestion) {
|
|
if (Array.isArray(me.options.initialSuggestions)) {
|
|
me.options.initialSuggestions.forEach(function (suggestion) {
|
|
if (typeof suggestion !== 'object') {
|
|
// the suggestion is of wrong type and therefore ignored
|
|
return;
|
|
}
|
|
if (value.localeCompare(suggestion.text.slice(0, value.length), undefined, { sensitivity: 'base' }) === 0) {
|
|
// The suggestion starts with the query text the user entered and will be displayed
|
|
me.autocomplete.addSuggestion(suggestion);
|
|
}
|
|
});
|
|
if (me.autocomplete.suggestions.childNodes.length > 0) {
|
|
me.autocomplete.highlightSuggestionAtPosition(0);
|
|
}
|
|
else if (me.options.noMatchesText) {
|
|
me.autocomplete.addSuggestion({ value: '_no_match_', text: me.options.noMatchesText, description: null });
|
|
}
|
|
}
|
|
else if (me.options.suggestionsUri.length > 0) {
|
|
me.autocomplete.requestSuggestions(value);
|
|
}
|
|
}
|
|
});
|
|
this.container.tokenAutocomplete = this;
|
|
}
|
|
/**
|
|
* Searches the element given as a container for option elements and creates active tokens (when the option is marked selected)
|
|
* and suggestions (all options found) from these. During this all found options are removed from the DOM.
|
|
*/
|
|
TokenAutocomplete.prototype.parseTokensAndSuggestions = function () {
|
|
var initialTokens = [];
|
|
var initialSuggestions = [];
|
|
var options = this.container.querySelectorAll('option');
|
|
var me = this;
|
|
options.forEach(function (option) {
|
|
if (option.text != null) {
|
|
if (option.hasAttribute('selected')) {
|
|
initialTokens.push({ value: option.value, text: option.text });
|
|
}
|
|
initialSuggestions.push({ value: option.value, text: option.text, description: null });
|
|
}
|
|
me.container.removeChild(option);
|
|
});
|
|
if (initialTokens.length > 0) {
|
|
this.options.initialTokens = initialTokens;
|
|
}
|
|
if (initialSuggestions.length > 0) {
|
|
this.options.initialSuggestions = initialSuggestions;
|
|
}
|
|
};
|
|
/**
|
|
* Clears the currently present tokens and creates new ones from the given input value.
|
|
*
|
|
* @param {(Array\|string)} value - either the name of a single token or a list of tokens to create
|
|
*/
|
|
TokenAutocomplete.prototype.val = function (value) {
|
|
this.select.clear();
|
|
if (Array.isArray(value)) {
|
|
var me_1 = this;
|
|
value.forEach(function (token) {
|
|
if (typeof token === 'object') {
|
|
me_1.select.addToken(token.value, token.text);
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
this.select.addToken(value.value, value.text);
|
|
}
|
|
};
|
|
TokenAutocomplete.prototype.clearCurrentInput = function () {
|
|
this.textInput.textContent = '';
|
|
};
|
|
TokenAutocomplete.prototype.debug = function (state) {
|
|
if (state) {
|
|
this.log = console.log.bind(window.console);
|
|
}
|
|
else {
|
|
this.log = function () { };
|
|
}
|
|
};
|
|
var _a;
|
|
TokenAutocomplete.MultiSelect = /** @class */ (function () {
|
|
function class_1(parent) {
|
|
this.parent = parent;
|
|
this.container = parent.container;
|
|
this.options = parent.options;
|
|
}
|
|
/**
|
|
* Adds a token with the specified name to the list of currently prensent tokens displayed to the user and the hidden select.
|
|
*
|
|
* @param {string} tokenText - the name of the token to create
|
|
*/
|
|
class_1.prototype.addToken = function (tokenValue, tokenText) {
|
|
if (tokenValue === null || tokenText === null) {
|
|
return;
|
|
}
|
|
var option = document.createElement('option');
|
|
option.text = tokenText;
|
|
option.value = tokenValue;
|
|
option.setAttribute('selected', 'true');
|
|
option.setAttribute('data-text', tokenText);
|
|
option.setAttribute('data-value', tokenValue);
|
|
this.parent.hiddenSelect.add(option);
|
|
var token = document.createElement('span');
|
|
token.classList.add('token-autocomplete-token');
|
|
token.setAttribute('data-text', tokenText);
|
|
option.setAttribute('data-value', tokenValue);
|
|
token.textContent = tokenText;
|
|
var deleteToken = document.createElement('span');
|
|
deleteToken.classList.add('token-autocomplete-token-delete');
|
|
deleteToken.textContent = '\u00D7';
|
|
token.appendChild(deleteToken);
|
|
var me = this;
|
|
deleteToken.addEventListener('click', function (event) {
|
|
me.removeToken(token);
|
|
});
|
|
this.container.insertBefore(token, this.parent.textInput.nextSibling);
|
|
this.parent.log('added token', token);
|
|
};
|
|
/**
|
|
* Completely clears the currently present tokens from the field.
|
|
*/
|
|
class_1.prototype.clear = function () {
|
|
var tokens = this.container.querySelectorAll('.token-autocomplete-token');
|
|
var me = this;
|
|
tokens.forEach(function (token) { me.removeToken(token); });
|
|
};
|
|
/**
|
|
* Removes the last token in the list of currently present token. This is the last added token next to the input field.
|
|
*/
|
|
class_1.prototype.removeLastToken = function () {
|
|
var tokens = this.container.querySelectorAll('.token-autocomplete-token');
|
|
var token = tokens[tokens.length - 1];
|
|
this.removeToken(token);
|
|
};
|
|
/**
|
|
* Removes the specified token from the list of currently present tokens.
|
|
*
|
|
* @param {Element} token - the token to remove
|
|
*/
|
|
class_1.prototype.removeToken = function (token) {
|
|
var _a, _b;
|
|
this.container.removeChild(token);
|
|
var tokenText = token.getAttribute('data-text');
|
|
var hiddenOption = this.parent.hiddenSelect.querySelector('option[data-text="' + tokenText + '"]');
|
|
(_b = (_a = hiddenOption) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.removeChild(hiddenOption);
|
|
this.parent.log('removed token', token.textContent);
|
|
};
|
|
class_1.prototype.removeTokenWithText = function (tokenText) {
|
|
if (tokenText === null) {
|
|
return;
|
|
}
|
|
var token = this.container.querySelector('.token-autocomplete-token[data-text="' + tokenText + '"]');
|
|
if (token !== null) {
|
|
this.removeToken(token);
|
|
}
|
|
};
|
|
return class_1;
|
|
}());
|
|
TokenAutocomplete.Autocomplete = (_a = /** @class */ (function () {
|
|
function class_2(parent) {
|
|
this.parent = parent;
|
|
this.container = parent.container;
|
|
this.options = parent.options;
|
|
this.renderer = parent.options.suggestionRenderer;
|
|
this.suggestions = document.createElement('ul');
|
|
this.suggestions.id = this.container.id + '-suggestions';
|
|
this.suggestions.classList.add('token-autocomplete-suggestions');
|
|
this.container.appendChild(this.suggestions);
|
|
}
|
|
/**
|
|
* Hides the suggestions dropdown from the user.
|
|
*/
|
|
class_2.prototype.hideSuggestions = function () {
|
|
this.suggestions.style.display = '';
|
|
};
|
|
/**
|
|
* Shows the suggestions dropdown to the user.
|
|
*/
|
|
class_2.prototype.showSuggestions = function () {
|
|
this.suggestions.style.display = 'block';
|
|
};
|
|
class_2.prototype.highlightSuggestionAtPosition = function (index) {
|
|
var suggestions = this.suggestions.querySelectorAll('li');
|
|
suggestions.forEach(function (suggestion) {
|
|
suggestion.classList.remove('token-autocomplete-suggestion-highlighted');
|
|
});
|
|
suggestions[index].classList.add('token-autocomplete-suggestion-highlighted');
|
|
};
|
|
class_2.prototype.highlightSuggestion = function (suggestion) {
|
|
this.suggestions.querySelectorAll('li').forEach(function (suggestion) {
|
|
suggestion.classList.remove('token-autocomplete-suggestion-highlighted');
|
|
});
|
|
suggestion.classList.add('token-autocomplete-suggestion-highlighted');
|
|
};
|
|
/**
|
|
* Removes all previous suggestions from the dropdown.
|
|
*/
|
|
class_2.prototype.clearSuggestions = function () {
|
|
this.suggestions.innerHTML = '';
|
|
};
|
|
/**
|
|
* Loads suggestions matching the given query from the rest service behind the URI given as an option while initializing the field.
|
|
*
|
|
* @param query the query to search suggestions for
|
|
*/
|
|
class_2.prototype.requestSuggestions = function (query) {
|
|
var me = this;
|
|
var request = new XMLHttpRequest();
|
|
request.onload = function () {
|
|
if (Array.isArray(request.response)) {
|
|
request.response.forEach(function (suggestion) {
|
|
me.addSuggestion(suggestion);
|
|
});
|
|
}
|
|
};
|
|
request.open('GET', me.options.suggestionsUri + '?query=' + query, true);
|
|
request.responseType = 'json';
|
|
request.setRequestHeader('Content-type', 'application/json');
|
|
request.send();
|
|
};
|
|
/**
|
|
* Adds a suggestion with the given text matching the users input to the dropdown.
|
|
*
|
|
* @param {string} suggestionText - the text that should be displayed for the added suggestion
|
|
*/
|
|
class_2.prototype.addSuggestion = function (suggestion) {
|
|
var element = this.renderer(suggestion);
|
|
element.setAttribute('data-value', suggestion.value);
|
|
var me = this;
|
|
element.addEventListener('click', function (_event) {
|
|
if (suggestion.text == me.options.noMatchesText) {
|
|
return true;
|
|
}
|
|
if (element.classList.contains('token-autocomplete-suggestion-active')) {
|
|
me.parent.select.removeTokenWithText(suggestion.text);
|
|
}
|
|
else {
|
|
me.parent.select.addToken(suggestion.value, suggestion.text);
|
|
}
|
|
me.clearSuggestions();
|
|
me.hideSuggestions();
|
|
me.parent.clearCurrentInput();
|
|
});
|
|
if (this.container.querySelector('.token-autocomplete-token[data-text="' + suggestion.text + '"]') !== null) {
|
|
element.classList.add('token-autocomplete-suggestion-active');
|
|
}
|
|
this.suggestions.appendChild(element);
|
|
this.showSuggestions();
|
|
me.parent.log('added suggestion', suggestion);
|
|
};
|
|
return class_2;
|
|
}()),
|
|
_a.defaultRenderer = function (suggestion) {
|
|
var option = document.createElement('li');
|
|
option.textContent = suggestion.text;
|
|
if (suggestion.description) {
|
|
var description = document.createElement('small');
|
|
description.textContent = suggestion.description;
|
|
description.classList.add('token-autocomplete-suggestion-description');
|
|
option.appendChild(description);
|
|
}
|
|
return option;
|
|
},
|
|
_a);
|
|
return TokenAutocomplete;
|
|
}());
|
|
//# sourceMappingURL=token-autocomplete.js.map
|