From 78e3d36a5cc8e3875ceb6f38c826d3c03af3835a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Wed, 3 Aug 2022 22:44:38 +0200 Subject: [PATCH] Use tom-select as tagsinput input --- .../elements/tagsinput_controller.js | 46 +- assets/css/tagsinput.css | 64 -- assets/css/tom-select_extensions.css | 4 + assets/js/lib/tagsinput.js | 687 ------------------ package.json | 1 + src/Controller/TypeaheadController.php | 1 + src/Form/Part/PartBaseType.php | 2 +- src/Services/TagFinder.php | 4 +- yarn.lock | 12 + 9 files changed, 62 insertions(+), 759 deletions(-) delete mode 100644 assets/css/tagsinput.css create mode 100644 assets/css/tom-select_extensions.css delete mode 100644 assets/js/lib/tagsinput.js diff --git a/assets/controllers/elements/tagsinput_controller.js b/assets/controllers/elements/tagsinput_controller.js index 779e82da..4454089f 100644 --- a/assets/controllers/elements/tagsinput_controller.js +++ b/assets/controllers/elements/tagsinput_controller.js @@ -1,13 +1,47 @@ import {Controller} from "@hotwired/stimulus"; -import Bloodhound from "corejs-typeahead/dist/bloodhound"; -import 'corejs-typeahead'; -import '../../js/lib/tagsinput'; -import '../../css/tagsinput.css' +import "tom-select/dist/css/tom-select.bootstrap5.css"; +import '../../css/tom-select_extensions.css'; +import TomSelect from "tom-select"; export default class extends Controller { + _tomSelect; + connect() { + + let settings = { + plugins: { + remove_button:{ + } + }, + persistent: false, + createOnBlur: true, + create: true, + }; + if(this.element.dataset.autocomplete) { + const base_url = this.element.dataset.autocomplete; + settings.load = (query, callback) => { + if(query.length < 2){ + callback(); + return; + } + const url = base_url.replace('__QUERY__', encodeURIComponent(query)); + + fetch(url) + .then(response => response.json()) + .then(json => { + const data = json.map(x => {return {"value": x, "text": x}}); + callback(data); + }).catch(()=>{ + callback(); + }); + } + } + + this._tomSelect = new TomSelect(this.element, settings); + + /*if(this.element.dataset.autocomplete) { const engine = new Bloodhound({ //@ts-ignore datumTokenizer: Bloodhound.tokenizers.obj.whitespace(''), @@ -27,6 +61,8 @@ export default class extends Controller { }); } else { // Init tagsinput without typeahead $(this.element).tagsinput(); - } + }*/ + + } } \ No newline at end of file diff --git a/assets/css/tagsinput.css b/assets/css/tagsinput.css deleted file mode 100644 index e3ff2c34..00000000 --- a/assets/css/tagsinput.css +++ /dev/null @@ -1,64 +0,0 @@ -/* - * bootstrap-tagsinput v0.8.0 - * Modified by Jan Böhmer 2019 - * (Added some margin between the tags. - */ - -.bootstrap-tagsinput { - background-color: #fff; - border: 1px solid #ccc; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - display: inline-block; - padding: 4px 6px; - color: #555; - vertical-align: middle; - border-radius: 4px; - width: 100%; - line-height: 22px; - cursor: text; -} -.bootstrap-tagsinput input { - border: none; - box-shadow: none; - outline: none; - background-color: transparent; - padding: 0 6px; - margin: 0; - width: auto; - max-width: inherit; -} -.bootstrap-tagsinput.form-control input::-moz-placeholder { - color: #777; - opacity: 1; -} -.bootstrap-tagsinput.form-control input:-ms-input-placeholder { - color: #777; -} -.bootstrap-tagsinput.form-control input::-webkit-input-placeholder { - color: #777; -} -.bootstrap-tagsinput input:focus { - border: none; - box-shadow: none; -} -.bootstrap-tagsinput .badge { - margin: 2px 0.2em; - padding:5px 8px; -} -.bootstrap-tagsinput .badge [data-role="remove"] { - margin-left: 8px; - cursor: pointer; -} -.bootstrap-tagsinput .badge [data-role="remove"]:after { - content: "×"; - padding: 0px 4px; - background-color:rgba(0, 0, 0, 0.1); - border-radius:50%; - font-size:13px -} -.bootstrap-tagsinput .badge [data-role="remove"]:hover:after { - - background-color:rgba(0, 0, 0, 0.62);} -.bootstrap-tagsinput .badge [data-role="remove"]:hover:active { - box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); -} diff --git a/assets/css/tom-select_extensions.css b/assets/css/tom-select_extensions.css new file mode 100644 index 00000000..6b57888c --- /dev/null +++ b/assets/css/tom-select_extensions.css @@ -0,0 +1,4 @@ +.tagsinput.ts-wrapper.multi .ts-control > div { + background: var(--bs-secondary); + color: var(--bs-white); +} \ No newline at end of file diff --git a/assets/js/lib/tagsinput.js b/assets/js/lib/tagsinput.js deleted file mode 100644 index 71a9015c..00000000 --- a/assets/js/lib/tagsinput.js +++ /dev/null @@ -1,687 +0,0 @@ -/* - * bootstrap-tagsinput v0.8.0 - * - */ - -(function ($) { - "use strict"; - - var defaultOptions = { - tagClass: function(item) { - return 'badge bg-info'; - }, - focusClass: 'focus', - itemValue: function(item) { - return item ? item.toString() : item; - }, - itemText: function(item) { - return this.itemValue(item); - }, - itemTitle: function(item) { - return null; - }, - freeInput: true, - addOnBlur: true, - maxTags: undefined, - maxChars: undefined, - confirmKeys: [13, 44], - delimiter: ',', - delimiterRegex: null, - cancelConfirmKeysOnEmpty: false, - onTagExists: function(item, $tag) { - $tag.hide().fadeIn(); - }, - trimValue: false, - allowDuplicates: false, - triggerChange: true - }; - - /** - * Constructor function - */ - function TagsInput(element, options) { - this.isInit = true; - this.itemsArray = []; - - this.$element = $(element); - this.$element.addClass('sr-only'); - - this.isSelect = (element.tagName === 'SELECT'); - this.multiple = (this.isSelect && element.hasAttribute('multiple')); - this.objectItems = options && options.itemValue; - this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : ''; - this.inputSize = Math.max(1, this.placeholderText.length); - - this.$container = $('
'); - this.$input = $('').appendTo(this.$container); - - this.$element.before(this.$container); - - this.build(options); - this.isInit = false; - } - - TagsInput.prototype = { - constructor: TagsInput, - - /** - * Adds the given item as a new tag. Pass true to dontPushVal to prevent - * updating the elements val() - */ - add: function(item, dontPushVal, options) { - var self = this; - - if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags) - return; - - // Ignore falsey values, except false - if (item !== false && !item) - return; - - // Trim value - if (typeof item === "string" && self.options.trimValue) { - item = $.trim(item); - } - - // Throw an error when trying to add an object while the itemValue option was not set - if (typeof item === "object" && !self.objectItems) - throw("Can't add objects when itemValue option is not set"); - - // Ignore strings only containg whitespace - if (item.toString().match(/^\s*$/)) - return; - - // If SELECT but not multiple, remove current tag - if (self.isSelect && !self.multiple && self.itemsArray.length > 0) - self.remove(self.itemsArray[0]); - - if (typeof item === "string" && this.$element[0].tagName === 'INPUT') { - var delimiter = (self.options.delimiterRegex) ? self.options.delimiterRegex : self.options.delimiter; - var items = item.split(delimiter); - if (items.length > 1) { - for (var i = 0; i < items.length; i++) { - this.add(items[i], true); - } - - if (!dontPushVal) - self.pushVal(self.options.triggerChange); - return; - } - } - - var itemValue = self.options.itemValue(item), - itemText = self.options.itemText(item), - tagClass = self.options.tagClass(item), - itemTitle = self.options.itemTitle(item); - - // Ignore items allready added - var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0]; - if (existing && !self.options.allowDuplicates) { - // Invoke onTagExists - if (self.options.onTagExists) { - var $existingTag = $(".badge", self.$container).filter(function() { return $(this).data("item") === existing; }); - self.options.onTagExists(item, $existingTag); - } - return; - } - - // if length greater than limit - if (self.items().toString().length + item.length + 1 > self.options.maxInputLength) - return; - - // raise beforeItemAdd arg - var beforeItemAddEvent = $.Event('beforeItemAdd', { item: item, cancel: false, options: options}); - self.$element.trigger(beforeItemAddEvent); - if (beforeItemAddEvent.cancel) - return; - - // register item in internal array and map - self.itemsArray.push(item); - - // add a tag element - - var $tag = $('' + htmlEncode(itemText) + ''); - $tag.data('item', item); - self.findInputWrapper().before($tag); - - // Check to see if the tag exists in its raw or uri-encoded form - var optionExists = ( - $('option[value="' + encodeURIComponent(itemValue) + '"]', self.$element).length || - $('option[value="' + htmlEncode(itemValue) + '"]', self.$element).length - ); - - // add