diff --git a/public/js/autosuggest.js b/public/js/autosuggest.js index 066ea1b7..76ab8208 100644 --- a/public/js/autosuggest.js +++ b/public/js/autosuggest.js @@ -13,164 +13,165 @@ provided that the following conditions are met: 'use strict'; -function AutoSuggest(opt) { - if (typeof opt.inputElement === "undefined" || typeof opt.listElement === "undefined" || typeof opt.baseUrl === "undefined" || typeof opt.appendCallback === "undefined") { - return; +class AutoSuggest { + constructor(opt) { + if (typeof opt.inputElement === "undefined" || typeof opt.listElement === "undefined" || + typeof opt.baseUrl === "undefined" || typeof opt.appendCallback === "undefined") { + return; + } + + this.input = document.getElementById(opt.inputElement); + this.input.autocomplete = "off"; + this.list = document.getElementById(opt.listElement); + this.appendCallback = opt.appendCallback; + this.baseurl = opt.baseUrl; + + this.input.addEventListener('keydown', event => this.doSelection(event), false); + this.input.addEventListener('keyup', event => this.onType(event), false); } - this.input = document.getElementById(opt.inputElement); - this.input.autocomplete = "off"; - this.list = document.getElementById(opt.listElement); - this.appendCallback = opt.appendCallback; - this.baseurl = opt.baseUrl; + doSelection(event) { + if (typeof this.container === "undefined" || this.container.children.length === 0) { + return; + } - var self = this; - this.input.addEventListener('keydown', function(event) { - self.doSelection(event); - }, false); - this.input.addEventListener('keyup', function(event) { - self.onType(this, event); - }, false); + switch (event.key) { + case 'Enter': + event.preventDefault(); + this.container.children[this.selectedIndex].click(); + break; + + case 'ArrowUp': + case 'ArrowDown': + event.preventDefault(); + this.findSelectedElement().className = ''; + this.selectedIndex += event.key === 'ArrowUp' ? -1 : 1; + if (this.selectedIndex < 0) { + this.selectedIndex = this.container.children.length - 1; + } else if (this.selectedIndex === this.container.children.length) { + this.selectedIndex = 0; + } + let new_el = this.findSelectedElement().className = 'selected'; + break; + } + }; + + findSelectedElement() { + return this.container.children[this.selectedIndex]; + }; + + onType(event) { + if (['Enter', 'ArrowDown', 'ArrowUp'].indexOf(event.key) !== -1) { + return; + } + + let tokens = event.target.value.split(/\s+/).filter(token => token.length >= 2); + + if (tokens.length === 0) { + if (typeof this.container !== "undefined") { + this.clearContainer(); + } + return false; + } + + let request_uri = this.baseurl + '/suggest/?type=tags&data=' + window.encodeURIComponent(tokens.join(" ")); + let request = new HttpRequest('get', request_uri, {}, this.onReceive, this); + }; + + onReceive(response, self) { + self.openContainer(); + self.clearContainer(); + self.fillContainer(response); + }; + + openContainer() { + if (this.container) { + if (!this.container.parentNode) { + this.input.parentNode.appendChild(this.container); + } + return this.container; + } + + this.container = document.createElement('ul'); + this.container.className = 'autosuggest'; + this.input.parentNode.appendChild(this.container); + return this.container; + }; + + clearContainer() { + while (this.container.children.length > 0) { + this.container.removeChild(this.container.children[0]); + } + }; + + clearInput() { + this.input.value = ""; + this.input.focus(); + }; + + closeContainer() { + this.container.parentNode.removeChild(this.container); + }; + + fillContainer(response) { + this.selectedIndex = 0; + + let query = this.input.value.trim().replace(/[\-\[\]{}()*+?.,\\\/^\$|#]/g, ' '); + let query_tokens = query.split(/ +/).sort((a,b) => a.length - b.length); + + response.items.forEach((item, i) => { + let node = document.createElement('li'); + node.innerHTML = this.highlightMatches(query_tokens, item.label); + node.jsondata = item; + node.addEventListener('click', event => { + this.appendCallback(event.target.jsondata); + this.closeContainer(); + this.clearInput(); + }); + this.container.appendChild(node); + if (this.container.children.length === 1) { + node.className = 'selected'; + } + }); + }; + + highlightMatches(query_tokens, item) { + let itemTokens = item.split(/ +/); + let queryTokens = new RegExp('(' + query_tokens.join('\|') + ')', 'i'); + itemTokens.forEach((token, index) => { + item = item.replace(token, token.replace(queryTokens, ($1, match) => '' + match + '')); + }); + return item; + }; } -AutoSuggest.prototype.doSelection = function(event) { - if (typeof this.container === "undefined" || this.container.children.length === 0) { - return; +class TagAutoSuggest extends AutoSuggest { + constructor(opt) { + super(opt); + this.type = "tags"; } - switch (event.keyCode) { - case 13: // Enter - event.preventDefault(); - this.container.children[this.selectedIndex].click(); - break; + fillContainer(response) { + if (response.items.length > 0) { + super.fillContainer.call(this, response); + } else { + let node = document.createElement('li') + node.innerHTML = "Tag does not exist yet. Create it?"; - case 38: // Arrow up - case 40: // Arrow down - event.preventDefault(); - this.findSelectedElement().className = ''; - this.selectedIndex += event.keyCode === 38 ? -1 : 1; - if (this.selectedIndex < 0) { - this.selectedIndex = this.container.children.length - 1; - } else if (this.selectedIndex === this.container.children.length) { - this.selectedIndex = 0; - } - var new_el = this.findSelectedElement().className = 'selected'; - break; - } -}; + node.addEventListener('click', event => { + this.createNewTag(response => this.appendCallback(response)); + this.closeContainer(); + this.clearInput(); + }); -AutoSuggest.prototype.findSelectedElement = function() { - return this.container.children[this.selectedIndex]; -}; - -AutoSuggest.prototype.onType = function(input, event) { - if (event.keyCode === 13 || event.keyCode === 38 || event.keyCode === 40) { - return; - } - - var tokens = input.value.split(/\s+/).filter(function(token) { - return token.length >= 2; - }); - - if (tokens.length === 0) { - if (typeof this.container !== "undefined") { - this.clearContainer(); - } - return false; - } - - var request_uri = this.baseurl + '/suggest/?type=tags&data=' + window.encodeURIComponent(tokens.join(" ")); - var request = new HttpRequest('get', request_uri, {}, this.onReceive, this); -}; - -AutoSuggest.prototype.onReceive = function(response, self) { - self.openContainer(); - self.clearContainer(); - self.fillContainer(response); -}; - -AutoSuggest.prototype.openContainer = function() { - if (this.container) { - if (!this.container.parentNode) { - this.input.parentNode.appendChild(this.container); - } - return this.container; - } - - this.container = document.createElement('ul'); - this.container.className = 'autosuggest'; - this.input.parentNode.appendChild(this.container); - return this.container; -}; - -AutoSuggest.prototype.clearContainer = function() { - while (this.container.children.length > 0) { - this.container.removeChild(this.container.children[0]); - } -}; - -AutoSuggest.prototype.clearInput = function() { - this.input.value = ""; - this.input.focus(); -}; - -AutoSuggest.prototype.closeContainer = function() { - this.container.parentNode.removeChild(this.container); -}; - -AutoSuggest.prototype.fillContainer = function(response) { - var self = this; - this.selectedIndex = 0; - response.items.forEach(function(item, i) { - var node = document.createElement('li'); - var text = document.createTextNode(item.label); - node.jsondata = item; - node.addEventListener('click', function(event) { - self.appendCallback(this.jsondata); - self.closeContainer(); - self.clearInput(); - }); - node.appendChild(text); - self.container.appendChild(node); - if (self.container.children.length === 1) { + this.container.appendChild(node); + this.selectedIndex = 0; node.className = 'selected'; } - }); -}; + }; - -function TagAutoSuggest(opt) { - AutoSuggest.prototype.constructor.call(this, opt); - this.type = "tags"; -} - -TagAutoSuggest.prototype = Object.create(AutoSuggest.prototype); - -TagAutoSuggest.prototype.constructor = TagAutoSuggest; - -TagAutoSuggest.prototype.fillContainer = function(response) { - if (response.items.length > 0) { - AutoSuggest.prototype.fillContainer.call(this, response); - } else { - var node = document.createElement('li') - node.innerHTML = "Tag does not exist yet. Create it?"; - - var self = this; - node.addEventListener('click', function(event) { - self.createNewTag(function(response) { - self.appendCallback(response); - }); - self.closeContainer(); - self.clearInput(); - }); - - self.container.appendChild(node); - this.selectedIndex = 0; - node.className = 'selected'; + createNewTag(callback) { + let request_uri = this.baseurl + '/suggest/?type=createtag'; + let request = new HttpRequest('post', request_uri, 'tag=' + encodeURIComponent(this.input.value), callback, this); } -}; - -TagAutoSuggest.prototype.createNewTag = function(callback) { - var request_uri = this.baseurl + '/suggest/?type=createtag'; - var request = new HttpRequest('post', request_uri, 'tag=' + encodeURIComponent(this.input.value), callback, this); }