Skip to content

Commit

Permalink
A custom toolbox item remains available in a question selector drop-d…
Browse files Browse the repository at this point in the history
…own after the item was removed from a toolbox (#5371)

* resolve #5363 A custom toolbox item remains available in a question selector drop-down after the item was removed from a toolbox

* work for #5363

* work for #5363 fix unit-tests

* work for #5363

---------

Co-authored-by: OlgaLarina <olga.larina.dev@gmail.com>
  • Loading branch information
OlgaLarina and OlgaLarina committed Apr 4, 2024
1 parent a33faa5 commit 98fc8be
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 66 deletions.
87 changes: 52 additions & 35 deletions packages/survey-creator-core/src/components/question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
QuestionSelectBase,
createDropdownActionModel,
CssClassBuilder,
QuestionPanelDynamicModel
QuestionPanelDynamicModel,
ListModel
} from "survey-core";
import { SurveyCreatorModel } from "../creator-base";
import { editorLocalization, getLocString } from "../editorLocalization";
Expand Down Expand Up @@ -329,10 +330,24 @@ export class QuestionAdornerViewModel extends SurveyElementAdornerBase {
}

private createConvertToAction() {
const availableTypes = this.getConvertToTypesActions();
const allowChangeType: boolean = availableTypes.length > 0;
const newAction = this.createDropdownModel("convertTo", availableTypes,
allowChangeType, 0, this.currentType,
const actions = this.getConvertToTypesActions();
const allowChangeType: boolean = actions.length > 0;
const selItem = this.getSelectedItem(actions, this.currentType);
let actionTitle = !!selItem ? selItem.title : editorLocalization.getString("qt." + this.currentType);

const actionData: IAction = {
id: "convertTo",
enabled: allowChangeType,
visibleIndex: 0,
title: actionTitle,
iconName: this.creator.toolbox.getItemByName(this.element.getType())?.iconName
};
const newAction = this.createDropdownModel(actionData, actions,
(listModel: ListModel) => {
const newItems = this.getConvertToTypesActions();
listModel.setItems(newItems);
listModel.selectedItem = this.getSelectedItem(newItems, this.currentType);
},
(item: any) => {
this.creator.convertCurrentQuestion(item.id);
});
Expand All @@ -347,30 +362,34 @@ export class QuestionAdornerViewModel extends SurveyElementAdornerBase {
const propName = prop.name;
const questionSubType = this.surveyElement.getPropertyValue(propName);
const items = prop.getChoices(this.surveyElement, (chs: any) => { });
const availableTypes = [];
items.forEach(item => {
availableTypes.push({ id: item, title: editorLocalization.getPropertyValueInEditor(prop.name, item) });
});
const newAction = this.createDropdownModel("convertInputType", availableTypes, true, 1, questionSubType,

const getAvailableTypes = () => {
const availableTypes = [];
items.forEach(item => {
availableTypes.push({ id: item, title: editorLocalization.getPropertyValueInEditor(prop.name, item) });
});
return availableTypes;
};
const actionData: IAction = {
id: "convertInputType",
visibleIndex: 1,
title: editorLocalization.getPropertyValueInEditor(prop.name, questionSubType),
};
const newAction = this.createDropdownModel(actionData, getAvailableTypes(),
(listModel: ListModel) => {
const newItems = getAvailableTypes();
listModel.setItems(newItems);
listModel.selectedItem = this.getSelectedItem(newItems, this.surveyElement.getPropertyValue(propName));
},
(item: any) => {
const newValue = this.getUpdatedPropertyValue(propName, item.id);
this.surveyElement.setPropertyValue(propName, newValue);
let title = item.title;
if (newValue !== item.id) {
const popup = newAction.popupModel;
const list = popup.contentComponentData.model;
const newItem = list.getActionById(newValue);
if (newItem) {
title = newItem.title;
}
}
newAction.title = title;
});
newAction.disableShrink = true;
this.surveyElement.registerFunctionOnPropertyValueChanged(
propName,
() => {
const item = this.getSelectedItem(availableTypes, this.surveyElement.getPropertyValue(propName));
const item = this.getSelectedItem(getAvailableTypes(), this.surveyElement.getPropertyValue(propName));
if (!item) return;
const popup = newAction.popupModel;
const list = popup.contentComponentData.model;
Expand All @@ -386,30 +405,28 @@ export class QuestionAdornerViewModel extends SurveyElementAdornerBase {
const selectedItems = actions.filter(item => item.id === id);
return selectedItems.length > 0 ? selectedItems[0] : undefined;
}
private createDropdownModel(id: string, actions: IAction[],
enabled: boolean, index: number, selValue: string,
onSelectionChanged: (item: any) => void): Action {
const selItem = this.getSelectedItem(actions, selValue);
let actionTitle = !!selItem ? selItem.title : selValue;

private createDropdownModel(actionData: IAction, items: Array<IAction>, updateListModel: (listModel: ListModel) => void, onSelectionChanged: (item: any) => void): Action {
const newAction = createDropdownActionModel({
id: id,
id: actionData.id,
css: "sv-action--convertTo sv-action-bar-item--secondary",
iconName: id == "convertTo" ? this.creator.toolbox.getItemByName(this.element.getType())?.iconName : undefined,
iconName: actionData.iconName,
iconSize: 24,
title: actionTitle,
enabled: enabled,
visibleIndex: index,
title: actionData.title,
enabled: actionData.enabled,
visibleIndex: actionData.visibleIndex,
disableShrink: false,
location: "start",
action: (newType) => {
},
}, {
items: actions,
items: items,
onSelectionChanged: onSelectionChanged,
allowSelection: true,
selectedItem: selItem,
horizontalPosition: "center"
horizontalPosition: "center",
onShow: () => {
const listModel = newAction.popupModel.contentComponentData.model;
updateListModel(listModel);
},
});
newAction.popupModel.displayMode = this.creator.isTouch ? "overlay" : "popup";
newAction.data.locOwner = this.creator;
Expand Down
49 changes: 24 additions & 25 deletions packages/survey-creator-core/src/creator-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
QuestionSelectBase, QuestionRowModel, LocalizableString, ILocalizableString, ILocalizableOwner, PopupBaseViewModel,
EventBase, hasLicense, slk, settings as SurveySettings, Event, Helpers as SurveyHelpers, MatrixDropdownColumn, JsonObject,
dxSurveyService, ISurveyElement, PanelModelBase, surveyLocalization, QuestionMatrixDropdownModelBase, ITheme, Helpers,
chooseFiles
chooseFiles, createDropdownActionModel
} from "survey-core";
import { ICreatorPlugin, ISurveyCreatorOptions, settings, ICollectionItemAllowOperations } from "./creator-settings";
import { editorLocalization } from "./editorLocalization";
Expand Down Expand Up @@ -3379,34 +3379,33 @@ export class SurveyCreatorModel extends Base
}
public getQuestionTypeSelectorModel(beforeAdd: (type: string) => void, element?: SurveyElement) {
let panel = !!element && element.isPanel ? <PanelModel>element : null;
var availableTypes = this.getAvailableToolboxItems(element).map((item) => {
return this.createIActionBarItemByClass(item.name, item.title, item.iconName, item.needSeparator);
});
const listModel = new ListModel(
availableTypes,
(item: any) => {
this.currentAddQuestionType = item.id;
this.addNewQuestionInPage(beforeAdd, panel);
popupModel.toggleVisibility();
},
false
);
listModel.locOwner = this;
const popupModel = new PopupModel(
"sv-list", { model: listModel },
"bottom",
"center"
);
popupModel.displayMode = this.isTouch ? "overlay" : "popup";
const getActions = () => {
const availableTypes = this.getAvailableToolboxItems(element).map((item) => {
return this.createIActionBarItemByClass(item.name, item.title, item.iconName, item.needSeparator);
});
return availableTypes;
};

return <any>{
const newAction = createDropdownActionModel({
iconName: "icon-more",
title: this.getLocString("ed.addNewQuestion"),
action: () => {
popupModel.toggleVisibility();
}, {
items: getActions(),
onSelectionChanged: (item: any) => {
this.currentAddQuestionType = item.id;
this.addNewQuestionInPage(beforeAdd, panel);
},
popupModel: popupModel
};
onShow: () => {
const listModel = newAction.popupModel.contentComponentData.model;
listModel.setItems(getActions());
},
allowSelection: false,
verticalPosition: "bottom",
horizontalPosition: "center",
displayMode: this.isTouch ? "overlay" : "popup"
});

return newAction;
}

public getUpdatedPageAdornerFooterActions(pageAdorner: PageAdorner, actions: Array<IAction>) {
Expand Down
64 changes: 58 additions & 6 deletions packages/survey-creator-core/tests/creator-base.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1214,8 +1214,8 @@ test("Question type selector", (): any => {
expect(survey.getAllQuestions().length).toEqual(0);
expect(creator.addNewQuestionText).toEqual("Add Question");
const selectorModel = creator.getQuestionTypeSelectorModel(() => { });
const listModel: ListModel =
selectorModel.popupModel.contentComponentData.model;
const listModel: ListModel = selectorModel.popupModel.contentComponentData.model;
selectorModel.popupModel.toggleVisibility();
const ratingItem = listModel.actions.filter((item) => item.id == "rating")[0];
listModel.onItemClick(ratingItem);
expect(creator.addNewQuestionText).toEqual("Add Rating Scale");
Expand Down Expand Up @@ -1254,8 +1254,8 @@ test("Question type custom widgets", (): any => {
expect(survey.getAllQuestions().length).toEqual(0);
expect(creator.addNewQuestionText).toEqual("Add Question");
const selectorModel = creator.getQuestionTypeSelectorModel(() => { });
const listModel: ListModel =
selectorModel.popupModel.contentComponentData.model;
const listModel: ListModel = selectorModel.popupModel.contentComponentData.model;
selectorModel.popupModel.toggleVisibility();
const customItem = listModel.actions.filter((item) => item.id == "test_widget")[0];
expect(customItem.title).toEqual("Test Widget");
expect(customItem.iconName).toEqual("icon-editor");
Expand All @@ -1280,8 +1280,8 @@ test("Question type selector localization", (): any => {
const survey: SurveyModel = creator.survey;
expect(creator.addNewQuestionText).toEqual("Add New Question");
const selectorModel = creator.getQuestionTypeSelectorModel(() => { });
const listModel: ListModel =
selectorModel.popupModel.contentComponentData.model;
const listModel: ListModel = selectorModel.popupModel.contentComponentData.model;
selectorModel.popupModel.toggleVisibility();
const ratingItem = listModel.actions.filter((item) => item.id == "rating")[0];
listModel.onItemClick(ratingItem);
expect(creator.addNewQuestionText).toEqual("Add New Rating Scale");
Expand Down Expand Up @@ -2111,6 +2111,7 @@ test("ConvertTo, show the current question type selected", (): any => {
expect(items[0].id).toEqual("radiogroup");
const popup = questionModel.getActionById("convertTo").popupModel;
expect(popup).toBeTruthy();
popup.toggleVisibility();
const list = popup.contentComponentData.model;
expect(list).toBeTruthy();
expect(list.selectedItem).toBeTruthy();
Expand All @@ -2137,6 +2138,7 @@ test("ConvertTo, show it for a panel", (): any => {
expect(items).toHaveLength(21);
const popup = panelModel.getActionById("convertTo").popupModel;
expect(popup).toBeTruthy();
popup.toggleVisibility();
const list = popup.contentComponentData.model;
expect(list).toBeTruthy();
expect(list.selectedItem).toBeTruthy();
Expand Down Expand Up @@ -2215,6 +2217,51 @@ test("ConvertTo & addNewQuestion for panel & maxNestedPanels ", (): any => {
expect(creator.getAvailableToolboxItems()).toHaveLength(itemCount);
});

test("ConvertTo & addNewQuestion refresh items", (): any => {
const creator = new CreatorTester();
creator.JSON = {
elements: [{ type: "dropdown", name: "q1" }]
};
creator.onQuestionAdded.add((sender, options) => {
if (options.question.getType() === "text") {
creator.toolbox.removeItem("text");
}
});

const q1 = creator.survey.getQuestionByName("q1");
const q1AdornerModel = new QuestionAdornerViewModel(creator, q1, undefined);
const pageModel = creator.survey.pages[0];
const pageAdornerModel = new PageAdorner(creator, pageModel);
const convertToAction = q1AdornerModel.actionContainer.actions.filter(action => action.id === "convertTo")[0];
const questionTypeSelectorModel = pageAdornerModel.questionTypeSelectorModel;
const questionTypeSelectorListModel = questionTypeSelectorModel.popupModel.contentComponentData.model as ListModel;

expect(convertToAction.data.actions.length).toBe(21);
expect(questionTypeSelectorListModel.actions.length).toBe(21);

convertToAction.popupModel.toggleVisibility();
expect(convertToAction.data.actions.length).toBe(21);
convertToAction.popupModel.toggleVisibility();

questionTypeSelectorModel.popupModel.toggleVisibility();
expect(questionTypeSelectorListModel.actions.length).toBe(21);
questionTypeSelectorModel.popupModel.toggleVisibility();

pageModel.addNewQuestion("text", "q2");

convertToAction.popupModel.toggleVisibility();
expect(convertToAction.data.actions.length).toBe(20);
convertToAction.popupModel.toggleVisibility();

questionTypeSelectorModel.popupModel.toggleVisibility();
expect(questionTypeSelectorListModel.actions.length).toBe(20);
questionTypeSelectorModel.popupModel.toggleVisibility();

const q2AdornerModel = new QuestionAdornerViewModel(creator, creator.survey.getQuestionByName("q2"), undefined);
const convertToAction2 = q2AdornerModel.actionContainer.actions.filter(action => action.id === "convertTo")[0];
expect(convertToAction2.title).toBe("Single-Line Input");
});

test("ConverTo, change title of question item", (): any => {
const creator = new CreatorTester();
creator.toolbox.getItemByName("radiogroup").title = "Single selector";
Expand Down Expand Up @@ -2298,6 +2345,7 @@ test("convertInputType, change inputType for a text question", (): any => {
expect(action.title).toBe("Text");
const popup = action.popupModel;
expect(popup).toBeTruthy();
popup.toggleVisibility();
const list = popup.contentComponentData.model;
expect(list).toBeTruthy();
expect(list.selectedItem).toBeTruthy();
Expand Down Expand Up @@ -2973,6 +3021,7 @@ test("Add new question to Panel and Page", (): any => {

const selectorModelPanel = panelAdornerModel.questionTypeSelectorModel;
const listModelPanel: ListModel = selectorModelPanel.popupModel.contentComponentData.model;
selectorModelPanel.popupModel.toggleVisibility();
const ratingItem = listModelPanel.actions.filter((item) => item.id == "rating")[0];
listModelPanel.onItemClick(ratingItem);

Expand All @@ -2983,6 +3032,7 @@ test("Add new question to Panel and Page", (): any => {

const selectorModelPanel2 = panelAdornerModel2.questionTypeSelectorModel;
const listModelPanel2: ListModel = selectorModelPanel2.popupModel.contentComponentData.model;
selectorModelPanel2.popupModel.toggleVisibility();
const commentItem = listModelPanel2.actions.filter((item) => item.id == "comment")[0];
listModelPanel2.onItemClick(commentItem);

Expand All @@ -2993,6 +3043,7 @@ test("Add new question to Panel and Page", (): any => {

const selectorModelPage = pageAdornerModel.questionTypeSelectorModel;
const listModelPage: ListModel = selectorModelPage.popupModel.contentComponentData.model;
selectorModelPage.popupModel.toggleVisibility();
const rankingItem = listModelPage.actions.filter((item) => item.id == "ranking")[0];
listModelPage.onItemClick(rankingItem);

Expand All @@ -3003,6 +3054,7 @@ test("Add new question to Panel and Page", (): any => {

const selectorModelPage2 = pageAdornerModel2.questionTypeSelectorModel;
const listModelPage2: ListModel = selectorModelPage2.popupModel.contentComponentData.model;
selectorModelPage2.popupModel.toggleVisibility();
const htmlItem = listModelPage2.actions.filter((item) => item.id == "html")[0];
listModelPage2.onItemClick(htmlItem);

Expand Down
2 changes: 2 additions & 0 deletions packages/survey-creator-core/tests/creator-toolbox.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ test("Has one item type in convertTo", (): any => {
const items = questionModel.getConvertToTypesActions();
const popup = questionModel.getActionById("convertTo").popupModel;
expect(popup).toBeTruthy();
popup.toggleVisibility();
const list = popup.contentComponentData.model;
expect(list).toBeTruthy();
counter = 0;
Expand Down Expand Up @@ -240,6 +241,7 @@ test("Doesn't duplicate custom toolbox items with built-in ones in convertTo", (

const popup = questionModel.getActionById("convertTo").popupModel;
expect(popup).toBeTruthy();
popup.toggleVisibility();
const list = popup.contentComponentData.model;
expect(list).toBeTruthy();
counter = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ test("actions and creator.onPropertyValueChanging", () => {
expect(action).toBeTruthy();
const popup = action.popupModel;
expect(popup).toBeTruthy();
popup.toggleVisibility();
const list = popup.contentComponentData.model;
list.onSelectionChanged(list.actions.filter(item => item.id === "tel")[0]);
expect(q1.inputType).toBe("date");
Expand Down

0 comments on commit 98fc8be

Please sign in to comment.