Skip to content

Commit 2fc70b8

Browse files
Treeedjona
andauthored
Highlightable attachment paths (#849)
* made autocomplete controller allow selecting text and autocommit typed text on blur * moved click_to_edit and autoselect_typed into separate plugins --------- Co-authored-by: jona <[email protected]>
1 parent b724b05 commit 2fc70b8

File tree

3 files changed

+162
-0
lines changed

3 files changed

+162
-0
lines changed

assets/controllers/elements/attachment_autocomplete_controller.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
2323
import '../../css/components/tom-select_extensions.css';
2424
import TomSelect from "tom-select";
2525

26+
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
27+
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
28+
29+
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
30+
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
31+
2632
export default class extends Controller {
2733
_tomSelect;
2834

@@ -46,6 +52,11 @@ export default class extends Controller {
4652
}
4753
return '<div>' + escape(data.label) + '</div>';
4854
}
55+
},
56+
plugins: {
57+
'autoselect_typed': {},
58+
'click_to_edit': {},
59+
'remove_button': {}
4960
}
5061
};
5162

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Autoselect Typed plugin for Tomselect
3+
*
4+
* This plugin allows automatically selecting an option matching the typed text when the Tomselect element goes out of
5+
* focus (is blurred) and/or when the delimiter is typed.
6+
*
7+
* #select_on_blur option
8+
* Tomselect natively supports the "createOnBlur" option. This option picks up any remaining text in the input field
9+
* and uses it to create a new option and selects that option. It does behave a bit strangely though, in that it will
10+
* not select an already existing option when the input is blurred, so if you typed something that matches an option in
11+
* the list and then click outside the box (without pressing enter) the entered text is just removed (unless you have
12+
* allow duplicates on in which case it will create a new option).
13+
* This plugin fixes that, such that Tomselect will first try to select an option matching the remaining uncommitted
14+
* text and only when no matching option is found tries to create a new one (if createOnBlur and create is on)
15+
*
16+
* #select_on_delimiter option
17+
* Normally when typing the delimiter (space by default) Tomselect will try to create a new option (and select it) (if
18+
* create is on), but if the typed text matches an option (and allow duplicates is off) it refuses to react at all until
19+
* you press enter. With this option, the delimiter will also allow selecting an option, not just creating it.
20+
*/
21+
function select_current_input(self){
22+
if(self.isLocked){
23+
return
24+
}
25+
26+
const val = self.inputValue()
27+
if (self.options[val]) {
28+
self.addItem(val)
29+
self.setTextboxValue()
30+
}
31+
}
32+
33+
export default function(plugin_options_) {
34+
const plugin_options = Object.assign({
35+
//Autoselect the typed text when the input element goes out of focus
36+
select_on_blur: true,
37+
//Autoselect the typed text when the delimiter is typed
38+
select_on_delimiter: true,
39+
}, plugin_options_);
40+
41+
const self = this
42+
43+
if(plugin_options.select_on_blur) {
44+
this.hook("before", "onBlur", function () {
45+
select_current_input(self)
46+
})
47+
}
48+
49+
if(plugin_options.select_on_delimiter) {
50+
this.hook("before", "onKeyPress", function (e) {
51+
const character = String.fromCharCode(e.keyCode || e.which);
52+
if (self.settings.mode === 'multi' && character === self.settings.delimiter) {
53+
select_current_input(self)
54+
}
55+
})
56+
}
57+
58+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* click_to_edit plugin for Tomselect
3+
*
4+
* This plugin allows editing (and selecting text in) any selected item by clicking it.
5+
*
6+
* Usually, when the user typed some text and created an item in Tomselect that item cannot be edited anymore. To make
7+
* a change, the item has to be deleted and retyped completely. There is also generally no way to copy text out of a
8+
* tomselect item. The "restore_on_backspace" plugin improves that somewhat, by allowing the user to edit an item after
9+
* pressing backspace. However, it is somewhat confusing to first have to focus the field an then hit backspace in order
10+
* to copy a piece of text. It may also not be immediately obvious for editing.
11+
* This plugin transforms an item into editable text when it is clicked, e.g. when the user tries to place the caret
12+
* within an item or when they try to drag across the text to highlight it.
13+
* It also plays nice with the remove_button plugin which still removes (deselects) an option entirely.
14+
*
15+
* It is recommended to also enable the autoselect_typed plugin when using this plugin. Without it, the text in the
16+
* input field (i.e. the item that was just clicked) is lost when the user clicks outside the field. Also, when the user
17+
* clicks an option (making it text) and then tries to enter another one by entering the delimiter (e.g. space) nothing
18+
* happens until enter is pressed or the text is changed from what it was.
19+
*/
20+
21+
/**
22+
* Return a dom element from either a dom query string, jQuery object, a dom element or html string
23+
* https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
24+
*
25+
* param query should be {}
26+
*/
27+
const getDom = query => {
28+
if (query.jquery) {
29+
return query[0];
30+
}
31+
if (query instanceof HTMLElement) {
32+
return query;
33+
}
34+
if (isHtmlString(query)) {
35+
var tpl = document.createElement('template');
36+
tpl.innerHTML = query.trim(); // Never return a text node of whitespace as the result
37+
return tpl.content.firstChild;
38+
}
39+
return document.querySelector(query);
40+
};
41+
const isHtmlString = arg => {
42+
if (typeof arg === 'string' && arg.indexOf('<') > -1) {
43+
return true;
44+
}
45+
return false;
46+
};
47+
48+
function plugin(plugin_options_) {
49+
const self = this
50+
51+
const plugin_options = Object.assign({
52+
//If there is unsubmitted text in the input field, should that text be automatically used to select a matching
53+
//element? If this is off, clicking on item1 and then clicking on item2 will result in item1 being deselected
54+
auto_select_before_edit: true,
55+
//If there is unsubmitted text in the input field, should that text be automatically used to create a matching
56+
//element if no matching element was found or auto_select_before_edit is off?
57+
auto_create_before_edit: true,
58+
//customize this function to change which text the item is replaced with when clicking on it
59+
text: option => {
60+
return option[self.settings.labelField];
61+
}
62+
}, plugin_options_);
63+
64+
65+
self.hook('after', 'setupTemplates', () => {
66+
const orig_render_item = self.settings.render.item;
67+
self.settings.render.item = (data, escape) => {
68+
const item = getDom(orig_render_item.call(self, data, escape));
69+
70+
item.addEventListener('click', evt => {
71+
if (self.isLocked) {
72+
return;
73+
}
74+
const val = self.inputValue();
75+
76+
if (self.options[val]) {
77+
self.addItem(val)
78+
} else if (self.settings.create) {
79+
self.createItem();
80+
}
81+
const option = self.options[item.dataset.value]
82+
self.setTextboxValue(plugin_options.text.call(self, option));
83+
self.focus();
84+
self.removeItem(item);
85+
}
86+
);
87+
88+
return item;
89+
}
90+
});
91+
92+
}
93+
export { plugin as default };

0 commit comments

Comments
 (0)