Skip to content

Commit 21cbe8f

Browse files
committed
feat(pat-autosuggest): Add a configurable separator for multiple values.
1 parent 1b6c91c commit 21cbe8f

File tree

5 files changed

+230
-48
lines changed

5 files changed

+230
-48
lines changed

src/pat/auto-suggest/auto-suggest.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ parser.addArgument("prefill", function ($el) {
2424
parser.addArgument("prefill-json", ""); // JSON format for pre-filling
2525
parser.addArgument("words", "");
2626
parser.addArgument("words-json");
27+
parser.addArgument("separator", ","); // Separator for multiple values
2728

2829
// "selection-classes" allows you to add custom CSS classes to currently
2930
// selected elements.
@@ -50,6 +51,7 @@ export default Base.extend({
5051
this.options = parser.parse(this.el, this.options);
5152

5253
let config = {
54+
separator: this.options.separator,
5355
tokenSeparators: [","],
5456
openOnEnter: false,
5557
maximumSelectionSize: this.options.maxSelectionSize,
@@ -182,10 +184,13 @@ export default Base.extend({
182184
}
183185

184186
if (this.options.prefill?.length) {
185-
this.el.value = this.options.prefill.split(",");
187+
this.el.value = this.options.prefill
188+
.split(",")
189+
.map((it) => it.trim())
190+
.join(this.options.separator);
186191
config.initSelection = (element, callback) => {
187192
let data = [];
188-
const values = element.val().split(",");
193+
const values = element[0].value.split(this.options.separator);
189194
for (const value of values) {
190195
data.push({ id: value, text: value });
191196
}
@@ -215,7 +220,7 @@ export default Base.extend({
215220
ids.push(d);
216221
}
217222
}
218-
this.el.value = ids;
223+
this.el.value = ids.join(this.options.separator);
219224
config.initSelection = (element, callback) => {
220225
let _data = [];
221226
for (const d in data) {

src/pat/auto-suggest/auto-suggest.test.js

Lines changed: 158 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ describe("pat-autosuggest", function () {
9595
});
9696
});
9797

98-
describe("2.1 - Selected items", function () {
98+
describe("2 - Selected items", function () {
9999
it("2.1 - can be given custom CSS classes", async function () {
100100
testutils.createInputElement({
101101
data: 'words: apple,orange,pear; pre-fill: orange; selection-classes: {"orange": ["fruit", "orange"]}',
@@ -199,7 +199,8 @@ describe("pat-autosuggest", function () {
199199
{"id": "id-lemon", "text":"Lemon"}
200200
];
201201
prefill-json: {
202-
"id-orange": "Orange"
202+
"id-orange": "Orange",
203+
"id-lemon": "Lemon"
203204
};
204205
' />
205206
`;
@@ -209,9 +210,10 @@ describe("pat-autosuggest", function () {
209210
await utils.timeout(1); // wait a tick for async to settle.
210211

211212
let selected = document.querySelectorAll(".select2-search-choice");
212-
expect(selected.length).toBe(1);
213+
expect(selected.length).toBe(2);
213214
expect(selected[0].textContent.trim()).toBe("Orange");
214-
expect(input.value).toBe("id-orange");
215+
expect(selected[1].textContent.trim()).toBe("Lemon");
216+
expect(input.value).toBe("id-orange,id-lemon");
215217

216218
// NOTE: the keyboard event init key ``which`` is deprecated,
217219
// but that's what select2 3.5.1 is expecting.
@@ -224,18 +226,140 @@ describe("pat-autosuggest", function () {
224226
.dispatchEvent(new KeyboardEvent("keydown", { which: 8 }));
225227

226228
selected = document.querySelectorAll(".select2-search-choice");
227-
expect(selected.length).toBe(0);
229+
expect(selected.length).toBe(1);
230+
expect(selected[0].textContent.trim()).toBe("Orange");
228231

229232
document.querySelector(".select2-input").click();
230233
document.querySelector(".select2-result").dispatchEvent(events.mouseup_event()); // prettier-ignore
231234
document.querySelector(".select2-input").click();
232235
document.querySelector(".select2-result").dispatchEvent(events.mouseup_event()); // prettier-ignore
233236

234237
selected = document.querySelectorAll(".select2-search-choice");
238+
expect(selected.length).toBe(3);
239+
expect(selected[0].textContent.trim()).toBe("Orange");
240+
expect(selected[1].textContent.trim()).toBe("Apple");
241+
expect(selected[2].textContent.trim()).toBe("Lemon");
242+
expect(input.value).toBe("id-orange,id-apple,id-lemon");
243+
});
244+
245+
it("2.6 - items can be pre-filled without json.", async function () {
246+
document.body.innerHTML = `
247+
<input
248+
type="text"
249+
class="pat-autosuggest"
250+
data-pat-autosuggest="
251+
prefill: id-orange, id-lemon;
252+
" />
253+
`;
254+
255+
const input = document.querySelector("input");
256+
new pattern(input);
257+
await utils.timeout(1); // wait a tick for async to settle.
258+
259+
let selected = document.querySelectorAll(".select2-search-choice");
260+
expect(selected.length).toBe(2);
261+
expect(selected[0].textContent.trim()).toBe("id-orange");
262+
expect(selected[1].textContent.trim()).toBe("id-lemon");
263+
expect(input.value).toBe("id-orange,id-lemon");
264+
});
265+
266+
it("2.7.1 - use a custom separator for multiple items.", async function () {
267+
document.body.innerHTML = `
268+
<input
269+
type="text"
270+
class="pat-autosuggest"
271+
data-pat-autosuggest="
272+
words: apple, orange, pear;
273+
separator: |" />
274+
" />
275+
`;
276+
277+
const input = document.querySelector("input");
278+
new pattern(input);
279+
await utils.timeout(1); // wait a tick for async to settle.
280+
$(".select2-input").click();
281+
$(document.querySelector(".select2-result")).mouseup();
282+
$(".select2-input").click();
283+
$(document.querySelector(".select2-result")).mouseup();
284+
285+
const selected = document.querySelectorAll(".select2-search-choice");
286+
expect(selected.length).toBe(2);
287+
expect(selected[0].textContent.trim()).toBe("apple");
288+
expect(selected[1].textContent.trim()).toBe("orange");
289+
expect(input.value).toBe("apple|orange");
290+
});
291+
292+
it("2.7.2 - use another custom separator for multiple items.", async function () {
293+
document.body.innerHTML = `
294+
<input
295+
type="text"
296+
class="pat-autosuggest"
297+
data-pat-autosuggest="
298+
words: apple, orange, pear;
299+
separator: ;;" />
300+
" />
301+
`;
302+
303+
const input = document.querySelector("input");
304+
new pattern(input);
305+
await utils.timeout(1); // wait a tick for async to settle.
306+
$(".select2-input").click();
307+
$(document.querySelector(".select2-result")).mouseup();
308+
$(".select2-input").click();
309+
$(document.querySelector(".select2-result")).mouseup();
310+
311+
const selected = document.querySelectorAll(".select2-search-choice");
312+
expect(selected.length).toBe(2);
313+
expect(selected[0].textContent.trim()).toBe("apple");
314+
expect(selected[1].textContent.trim()).toBe("orange");
315+
expect(input.value).toBe("apple;orange");
316+
});
317+
318+
it("2.7.3 - use a custom separator and pre-fill with json.", async function () {
319+
document.body.innerHTML = `
320+
<input
321+
type="text"
322+
class="pat-autosuggest"
323+
data-pat-autosuggest='
324+
prefill-json: {
325+
"id-orange": "Orange",
326+
"id-lemon": "Lemon"
327+
};
328+
separator: ;;
329+
' />
330+
`;
331+
332+
const input = document.querySelector("input");
333+
new pattern(input);
334+
await utils.timeout(1); // wait a tick for async to settle.
335+
336+
let selected = document.querySelectorAll(".select2-search-choice");
337+
expect(selected.length).toBe(2);
338+
expect(selected[0].textContent.trim()).toBe("Orange");
339+
expect(selected[1].textContent.trim()).toBe("Lemon");
340+
expect(input.value).toBe("id-orange;id-lemon");
341+
});
342+
343+
it("2.7.4 - use a custom separator and pre-fill.", async function () {
344+
document.body.innerHTML = `
345+
<input
346+
type="text"
347+
class="pat-autosuggest"
348+
data-pat-autosuggest="
349+
prefill: id-orange, id-lemon;
350+
separator: ;;
351+
" />
352+
`;
353+
354+
const input = document.querySelector("input");
355+
new pattern(input);
356+
await utils.timeout(1); // wait a tick for async to settle.
357+
358+
let selected = document.querySelectorAll(".select2-search-choice");
235359
expect(selected.length).toBe(2);
236-
expect(selected[0].textContent.trim()).toBe("Apple");
237-
expect(selected[1].textContent.trim()).toBe("Orange");
238-
expect(input.value).toBe("id-apple,id-orange");
360+
expect(selected[0].textContent.trim()).toBe("id-orange");
361+
expect(selected[1].textContent.trim()).toBe("id-lemon");
362+
expect(input.value).toBe("id-orange;id-lemon");
239363
});
240364
});
241365

@@ -362,4 +486,30 @@ describe("pat-autosuggest", function () {
362486
expect(form.querySelectorAll("em.warning").length).toBe(1);
363487
});
364488
});
489+
490+
describe("5 - Pittfalls...", function () {
491+
it("5.1 - BEWARE! JSON structures must be valid JSON!", async function () {
492+
// The following contains invalid JSON. Note the comma after the last prefill item.
493+
document.body.innerHTML = `
494+
<input
495+
type="text"
496+
class="pat-autosuggest"
497+
data-pat-autosuggest='
498+
prefill-json: {
499+
"id-orange": "Orange",
500+
"id-lemon": "Lemon",
501+
};
502+
' />
503+
`;
504+
505+
const input = document.querySelector("input");
506+
new pattern(input);
507+
await utils.timeout(1); // wait a tick for async to settle.
508+
509+
let selected = document.querySelectorAll(".select2-search-choice");
510+
511+
// INVALID JSON! No prefilling should happen.
512+
expect(selected.length).toBe(0);
513+
});
514+
});
365515
});

src/pat/auto-suggest/data.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
[
22
{ "id": "john-snow", "text": "John Snow" },
3-
{ "id": "tywin-lannister", "text": "Tywin Lannister" }
3+
{ "id": "tywin-lannister", "text": "Tywin Lannister" },
4+
{ "id": "mary-rose", "text": "Mary Rose" },
5+
{ "id": "joe-black", "text": "Joe Black" }
46
]

src/pat/auto-suggest/documentation.md

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,17 @@ Pre-fill the input element with words in JSON format and don't allow the user to
3838

3939
You can customise the behaviour of a gallery through options in the `data-pat-auto-suggest` attribute.
4040

41-
| Property | Type | Default Value | Description |
42-
| -------------------- | ------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
43-
| ajax-data-type | String | "json" | In what format will AJAX fetched data be returned in? |
44-
| ajax-search-index | String | | The index or key which must be used to determine the value from the returned data. |
45-
| ajax-url | URL | | The URL which must be called via AJAX to fetch remote data. |
46-
| allow-new-words | Boolean | true | Besides the suggested words, also allow custom user-defined words to be entered. |
47-
| max-selection-size | Number | 0 | How many values are allowed? Provide a positive number or 0 for unlimited. |
48-
| placeholder | String | Enter text | The placeholder text for the form input. The `placeholder` attribute of the form element can also be used. |
49-
| prefill | List | | A list of values with which the form element must be filled in with. |
50-
| prefill-json | JSON | | A JSON object containing prefill values. We support two types of JSON data for prefill data:`{"john-snow": "John Snow", "tywin-lannister": "Tywin Lannister"}` or `[{"id": "john-snow", "text": "John Snow"}, {"id": "tywin-lannister", "text":"Tywin Lannister"}]` |
51-
| words | List | | A list of words which will be used as suggestions if they match with what's being typed by the user. |
52-
| words-json | JSON | | The suggested words in JSON format. |
53-
| minimum-input-length | Number | 2 | Defines the minimum amount of characters needed for a search. |
41+
| Property | Type | Default Value | Description |
42+
| -------------------- | ------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
43+
| ajax-data-type | String | "json" | In what format will AJAX fetched data be returned in? |
44+
| ajax-search-index | String | | The index or key which must be used to determine the value from the returned data. |
45+
| ajax-url | URL | | The URL which must be called via AJAX to fetch remote data. |
46+
| allow-new-words | Boolean | true | Besides the suggested words, also allow custom user-defined words to be entered. |
47+
| max-selection-size | Number | 0 | How many values are allowed? Provide a positive number or 0 for unlimited. |
48+
| placeholder | String | Enter text | The placeholder text for the form input. The `placeholder` attribute of the form element can also be used. |
49+
| prefill | List | | A comma separated list of values with which the form element must be filled in with. The `separator` option does not have an effect here. |
50+
| prefill-json | JSON | | A JSON object containing prefill values. We support two types of JSON data for prefill data:`{"john-snow": "John Snow", "tywin-lannister": "Tywin Lannister"}` or `[{"id": "john-snow", "text": "John Snow"}, {"id": "tywin-lannister", "text":"Tywin Lannister"}]` |
51+
| separator | String | "," | A separator character to separate multiple values from each other. This is used to initialize and write back the selection to the input field. The prefilled option is always separated by a comma. If you want to use a semicolon as separator do it like so: `separator: ;;` |
52+
| words | List | | A list of words which will be used as suggestions if they match with what's being typed by the user. |
53+
| words-json | JSON | | The suggested words in JSON format. |
54+
| minimum-input-length | Number | 2 | Defines the minimum amount of characters needed for a search. |

0 commit comments

Comments
 (0)