From 99031dbdbe22c8f6c66280c006bbe0261ad7954a Mon Sep 17 00:00:00 2001 From: Aleksey Novikov Date: Mon, 19 Feb 2024 10:36:09 +0300 Subject: [PATCH] #7868 searchMode property for Tagbox? (#7874) Fixes #7868 --- src/dropdownListModel.ts | 17 ++++++++------- src/dropdownMultiSelectListModel.ts | 1 + src/question_tagbox.ts | 13 +++++++++++- tests/question_tagbox_tests.ts | 32 +++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/dropdownListModel.ts b/src/dropdownListModel.ts index 314224d755..7dc6358caa 100644 --- a/src/dropdownListModel.ts +++ b/src/dropdownListModel.ts @@ -152,6 +152,15 @@ export class DropdownListModel extends Base { protected getAvailableItems(): Array { return this.question.visibleChoices; } + protected setOnTextSearchCallbackForListModel(listModel: ListModel) { + listModel.setOnTextSearchCallback((item: ItemValue, textToSearch: string) => { + if (this.filteredItems) return this.filteredItems.indexOf(item) >= 0; + let textInLow = item.text.toLocaleLowerCase(); + textInLow = settings.comparator.normalizeTextCallback(textInLow, "filter"); + const index = textInLow.indexOf(textToSearch.toLocaleLowerCase()); + return this.question.searchMode == "startsWith" ? index == 0 : index > -1; + }); + } protected createListModel(): ListModel { const visibleItems = this.getAvailableItems(); let _onSelectionChanged = this.onSelectionChanged; @@ -163,13 +172,7 @@ export class DropdownListModel extends Base { }; } const res = new ListModel(visibleItems, _onSelectionChanged, false, undefined, this.question.choicesLazyLoadEnabled ? this.listModelFilterStringChanged : undefined, this.listElementId); - res.setOnTextSearchCallback((item: ItemValue, textToSearch: string) => { - if (this.filteredItems) return this.filteredItems.indexOf(item) >= 0; - let textInLow = item.text.toLocaleLowerCase(); - textInLow = settings.comparator.normalizeTextCallback(textInLow, "filter"); - const index = textInLow.indexOf(textToSearch.toLocaleLowerCase()); - return this.question.searchMode == "startsWith" ? index == 0 : index > -1; - }); + this.setOnTextSearchCallbackForListModel(res); res.renderElements = false; res.forceShowFilter = true; res.areSameItemsCallback = (item1: IAction, item2: IAction): boolean => { diff --git a/src/dropdownMultiSelectListModel.ts b/src/dropdownMultiSelectListModel.ts index e44c522df3..713a87eace 100644 --- a/src/dropdownMultiSelectListModel.ts +++ b/src/dropdownMultiSelectListModel.ts @@ -63,6 +63,7 @@ export class DropdownMultiSelectListModel extends DropdownListModel { }; } const res = new MultiSelectListModel(visibleItems, _onSelectionChanged, false, undefined, this.question.choicesLazyLoadEnabled ? this.listModelFilterStringChanged : undefined, this.listElementId); + this.setOnTextSearchCallbackForListModel(res); res.forceShowFilter = true; return res; } diff --git a/src/question_tagbox.ts b/src/question_tagbox.ts index 5a9c355ec4..e6c843f297 100644 --- a/src/question_tagbox.ts +++ b/src/question_tagbox.ts @@ -45,6 +45,16 @@ export class QuestionTagboxModel extends QuestionCheckboxModel { this.dropdownListModel = new DropdownMultiSelectListModel(this); } } + /** + * Specifies a comparison operation used to filter the drop-down list. Applies only if [`searchEnabled`](#searchEnabled) is `true`. + * + * Possible values: + * + * - `"contains"` (default) + * - `"startsWith"` + * @see [SurveyModel.onChoicesSearch](https://surveyjs.io/form-library/documentation/api-reference/survey-data-model#onChoicesSearch) + */ + @property() searchMode: "contains" | "startsWith"; /** * Specifies whether to display a button that clears the selected value. @@ -242,7 +252,8 @@ Serializer.addClass( { name: "choicesLazyLoadPageSize:number", default: 25, visible: false }, { name: "hideSelectedItems:boolean", default: false }, { name: "closeOnSelect:boolean" }, - { name: "itemComponent", visible: false, default: "" } + { name: "itemComponent", visible: false, default: "" }, + { name: "searchMode", default: "contains", choices: ["contains", "startsWith"] } ], function () { return new QuestionTagboxModel(""); diff --git a/tests/question_tagbox_tests.ts b/tests/question_tagbox_tests.ts index 41a2c6d95c..a80a2e9006 100644 --- a/tests/question_tagbox_tests.ts +++ b/tests/question_tagbox_tests.ts @@ -5,6 +5,7 @@ import { PopupBaseViewModel } from "../src/popup-view-model"; import { _setIsTouch } from "../src/utils/devices"; import { settings } from "../src/settings"; import { QuestionMatrixDynamicModel } from "../src/question_matrixdynamic"; +import { ListModel } from "../src/list"; export default QUnit.module("Tagbox question"); @@ -1509,4 +1510,35 @@ QUnit.test("Check readOnly tagbox with markdown", function (assert) { assert.equal(q1.displayValue, "item1 | item2 | item3"); assert.equal(q1.locReadOnlyText.renderedHtml, "item1 | item2 | item3"); +}); + +QUnit.test("Tagbox searchmode filter options", (assert) => { + const survey = new SurveyModel({ + questions: [{ + type: "tagbox", + name: "question1", + searchEnabled: true, + searchMode: "startsWith", + choices: [ + "abc", + "abd", + "cab", + "efg" + ] + }] + }); + const question = survey.getAllQuestions()[0]; + assert.equal(question.searchMode, "startsWith"); + const dropdownListModel = question.dropdownListModel; + const list: ListModel = dropdownListModel.popupModel.contentComponentData.model as ListModel; + + dropdownListModel.filterString = "ab"; + const getfilteredItems = () => list.renderedActions.filter(item => list.isItemVisible(item)); + + assert.equal(list.renderedActions.length, 4); + assert.equal(getfilteredItems().length, 2); + + question.searchMode = "contains"; + assert.equal(list.renderedActions.length, 4); + assert.equal(getfilteredItems().length, 3); }); \ No newline at end of file