diff --git a/packages/survey-angular-ui/src/questions/text.component.html b/packages/survey-angular-ui/src/questions/text.component.html
index 68c8cafd51..f03cd056e0 100644
--- a/packages/survey-angular-ui/src/questions/text.component.html
+++ b/packages/survey-angular-ui/src/questions/text.component.html
@@ -8,7 +8,7 @@
{{ value }}
any): any {
const res = this.getPropertyValueWithoutDefault(name);
if (this.isPropertyEmpty(res)) {
const locStr = this.localizableStrings ? this.localizableStrings[name] : undefined;
if (locStr) return locStr.text;
if (defaultValue !== null && defaultValue !== undefined) return defaultValue;
+ if(!!calcFunc) {
+ const newVal = calcFunc();
+ if(newVal !== undefined) {
+ this.setPropertyValueDirectly(name, newVal);
+ return newVal;
+ }
+ }
const propDefaultValue = this.getDefaultPropertyValue(name);
if (propDefaultValue !== undefined) return propDefaultValue;
}
diff --git a/packages/survey-core/src/dropdownListModel.ts b/packages/survey-core/src/dropdownListModel.ts
index 9ea4070f24..327ede6f57 100644
--- a/packages/survey-core/src/dropdownListModel.ts
+++ b/packages/survey-core/src/dropdownListModel.ts
@@ -39,7 +39,6 @@ export class DropdownListModel extends Base {
}
private itemsSettings: { skip: number, take: number, totalCount: number, items: any[] } = { skip: 0, take: 0, totalCount: 0, items: [] };
protected listModel: ListModel;
- protected popupCssClasses = "sv-single-select-list";
protected listModelFilterStringChanged = (newValue: string) => {
if (this.filterString !== newValue) {
this.filterString = newValue;
@@ -134,7 +133,6 @@ export class DropdownListModel extends Base {
this.listModel.registerPropertyChangedHandlers(["showFilter"], () => {
this.updatePopupFocusFirstInputSelector();
});
- this._popupModel.cssClass = this.popupCssClasses;
this._popupModel.onVisibilityChanged.add((_, option: { isVisible: boolean }) => {
if (option.isVisible) {
this.listModel.renderElements = true;
@@ -251,8 +249,9 @@ export class DropdownListModel extends Base {
model.isAllDataLoaded = !this.question.choicesLazyLoadEnabled;
model.actions.forEach(a => a.disableTabStop = true);
}
+ protected getPopupCssClasses(): string { return "sv-single-select-list"; }
public updateCssClasses(popupCssClass: string, listCssClasses: any): void {
- this.popupModel.cssClass = new CssClassBuilder().append(popupCssClass).append(this.popupCssClasses).toString();
+ this.popupModel.cssClass = new CssClassBuilder().append(popupCssClass).append(this.getPopupCssClasses()).toString();
this.listModel.cssClasses = listCssClasses;
}
protected resetFilterString(): void {
@@ -413,7 +412,6 @@ export class DropdownListModel extends Base {
constructor(protected question: Question, protected onSelectionChanged?: (item: IAction, ...params: any[]) => void) {
super();
this.htmlCleanerElement = DomDocumentHelper.createElement("div") as HTMLDivElement;
- this.question.ariaExpanded = "false";
question.onPropertyChanged.add(this.questionPropertyChangedHandler);
this.showInputFieldComponent = this.question.showInputFieldComponent;
@@ -424,6 +422,8 @@ export class DropdownListModel extends Base {
this.setTextWrapEnabled(this.question.textWrapEnabled);
this.createPopup();
this.resetItemsSettings();
+ const classes = question.cssClasses;
+ this.updateCssClasses(classes.popup, classes.list);
}
get popupModel(): PopupModel {
diff --git a/packages/survey-core/src/dropdownMultiSelectListModel.ts b/packages/survey-core/src/dropdownMultiSelectListModel.ts
index 5b79ef68da..2e35397511 100644
--- a/packages/survey-core/src/dropdownMultiSelectListModel.ts
+++ b/packages/survey-core/src/dropdownMultiSelectListModel.ts
@@ -9,8 +9,6 @@ import { settings } from "./settings";
import { IsTouch } from "./utils/devices";
export class DropdownMultiSelectListModel extends DropdownListModel {
- protected popupCssClasses = "sv-multi-select-list";
-
@property({ defaultValue: "" }) filterStringPlaceholder: string;
@property({ defaultValue: true }) closeOnSelect: boolean;
@@ -41,6 +39,7 @@ export class DropdownMultiSelectListModel extends DropdownListModel {
return super.getFocusFirstInputSelector();
}
}
+ protected getPopupCssClasses(): string { return "sv-multi-select-list"; }
protected createListModel(): MultiSelectListModel {
const visibleItems = this.getAvailableItems();
let _onSelectionChanged = this.onSelectionChanged;
@@ -70,7 +69,6 @@ export class DropdownMultiSelectListModel extends DropdownListModel {
elementId: this.listElementId
};
const res = new MultiSelectListModel(listOptions);
- res.actions.forEach(a => a.disableTabStop = true);
this.setOnTextSearchCallbackForListModel(res);
res.forceShowFilter = true;
return res;
diff --git a/packages/survey-core/src/jsonobject.ts b/packages/survey-core/src/jsonobject.ts
index f7244a1419..dfe715a659 100644
--- a/packages/survey-core/src/jsonobject.ts
+++ b/packages/survey-core/src/jsonobject.ts
@@ -86,9 +86,11 @@ export function property(options: IPropertyDecoratorOptions = {}) {
set: function (val: any) {
const newValue = processComputedUpdater(this, val);
const prevValue = this.getPropertyValue(key);
- this.setPropertyValue(key, newValue);
- if (!!options && options.onSet) {
- options.onSet(newValue, this, prevValue);
+ if(newValue !== prevValue) {
+ this.setPropertyValue(key, newValue);
+ if (!!options && options.onSet) {
+ options.onSet(newValue, this, prevValue);
+ }
}
},
});
diff --git a/packages/survey-core/src/list.ts b/packages/survey-core/src/list.ts
index 0168699a34..cfa2b12b4a 100644
--- a/packages/survey-core/src/list.ts
+++ b/packages/survey-core/src/list.ts
@@ -120,6 +120,9 @@ export class ListModel extends ActionContainer
if (!!this.onFilterStringChangedCallback) {
this.onFilterStringChangedCallback(text);
}
+ this.updateIsEmpty();
+ }
+ private updateIsEmpty(): void {
this.isEmpty = this.renderedActions.filter(action => this.isItemVisible(action)).length === 0;
}
private scrollToItem(selector: string, ms = 0): void {
@@ -160,6 +163,7 @@ export class ListModel extends ActionContainer
(this as any)[key] = options[key];
}
});
+ this.updateActionsIds();
} else {
this.setItems(items as Array);
this.selectedItem = selectedItem;
@@ -173,13 +177,16 @@ export class ListModel extends ActionContainer
}
public setItems(items: Array, sortByVisibleIndex = true): void {
super.setItems(items, sortByVisibleIndex);
- if (this.elementId) {
- this.renderedActions.forEach((action: IAction) => { action.elementId = this.elementId + action.id; });
- }
+ this.updateActionsIds();
if (!this.isAllDataLoaded && !!this.actions.length) {
this.actions.push(this.loadingIndicator);
}
}
+ private updateActionsIds(): void {
+ if (this.elementId) {
+ this.renderedActions.forEach((action: IAction) => { action.elementId = this.elementId + action.id; });
+ }
+ }
public setSearchEnabled(newValue: boolean): void {
this.searchEnabled = newValue;
this.showSearchClearButton = newValue;
@@ -316,7 +323,11 @@ export class ListModel extends ActionContainer
}
public onPointerDown(event: PointerEvent, item: any) { }
public refresh(): void { // used in popup
- this.filterString = "";
+ if(this.filterString !== "") {
+ this.filterString = "";
+ } else {
+ this.updateIsEmpty();
+ }
this.resetFocusedItem();
}
public onClickSearchClearButton(event: any) {
diff --git a/packages/survey-core/src/page.ts b/packages/survey-core/src/page.ts
index 7059cf2eda..598c580d7a 100644
--- a/packages/survey-core/src/page.ts
+++ b/packages/survey-core/src/page.ts
@@ -108,10 +108,6 @@ export class PageModel extends PanelModelBase implements IPage {
this.removeSelfFromList(this.survey.pages);
}
}
- public onFirstRendering(): void {
- if (this.wasShown) return;
- super.onFirstRendering();
- }
/**
* The visible index of the page. It has values from 0 to visible page count - 1.
* @see SurveyModel.visiblePages
diff --git a/packages/survey-core/src/panel.ts b/packages/survey-core/src/panel.ts
index f50de69d30..dcec2829e6 100644
--- a/packages/survey-core/src/panel.ts
+++ b/packages/survey-core/src/panel.ts
@@ -396,7 +396,7 @@ export class PanelModelBase extends SurveyElement
}
);
this.registerPropertyChangedHandlers(["title"], () => {
- this.calcHasTextInTitle();
+ this.resetHasTextInTitle();
});
this.dragDropPanelHelper = new DragDropPanelHelperV1(this);
@@ -404,7 +404,8 @@ export class PanelModelBase extends SurveyElement
public getType(): string {
return "panelbase";
}
- public setSurveyImpl(value: ISurveyImpl, isLight?: boolean) {
+ public setSurveyImpl(value: ISurveyImpl, isLight?: boolean): void {
+ //if(this.surveyImpl === value) return; TODO refactor
this.blockAnimations();
super.setSurveyImpl(value, isLight);
if (this.isDesignMode) this.onVisibleChanged();
@@ -425,11 +426,12 @@ export class PanelModelBase extends SurveyElement
@property({ defaultValue: true }) showTitle: boolean;
- @property({ defaultValue: false }) public hasTextInTitle: boolean;
- protected calcHasTextInTitle(): void {
- this.hasTextInTitle = !!this.title;
+ public get hasTextInTitle(): boolean {
+ return this.getPropertyValue("hasTextInTitle", undefined, (): boolean => !!this.title);
+ }
+ private resetHasTextInTitle(): void {
+ this.resetPropertyValue("hasTextInTitle");
}
-
get hasTitle(): boolean {
return (
(this.canShowTitle(this.survey) && (this.hasTextInTitle || this.locTitle.textOrHtml.length > 0)) ||
@@ -1357,14 +1359,11 @@ export class PanelModelBase extends SurveyElement
}
this.onElementVisibilityChanged(this);
this.releaseAnimations();
- this.calcHasTextInTitle();
}
- public onFirstRendering(): void {
- super.onFirstRendering();
- for (var i = 0; i < this.elements.length; i++) {
- this.elements[i].onFirstRendering();
- }
+ protected onFirstRenderingCore(): void {
+ super.onFirstRenderingCore();
this.onRowsChanged();
+ this.elements.forEach(el => el.onFirstRendering());
}
public updateRows(): void {
if (this.isLoadingFromJson) return;
@@ -1759,22 +1758,20 @@ export class PanelModelBase extends SurveyElement
protected getPanelStartIndex(index: number): number {
return index;
}
- protected isContinueNumbering() {
- return true;
- }
+ protected isContinueNumbering(): boolean { return true; }
public get isReadOnly(): boolean {
var isParentReadOnly = !!this.parent && this.parent.isReadOnly;
var isSurveyReadOnly = !!this.survey && this.survey.isDisplayMode;
return this.readOnly || isParentReadOnly || isSurveyReadOnly;
}
- protected onReadOnlyChanged() {
+ protected onReadOnlyChanged(): void {
for (var i = 0; i < this.elements.length; i++) {
var el = (this.elements[i]);
el.setPropertyValue("isReadOnly", el.isReadOnly);
}
super.onReadOnlyChanged();
}
- public updateElementCss(reNew?: boolean) {
+ public updateElementCss(reNew?: boolean): void {
super.updateElementCss(reNew);
for (let i = 0; i < this.elements.length; i++) {
const el = (this.elements[i]);
@@ -2068,7 +2065,7 @@ export class PanelModel extends PanelModelBase implements IElement {
}
});
this.registerPropertyChangedHandlers(
- ["indent", "innerIndent", "rightIndent"], () => { this.onIndentChanged(); });
+ ["indent", "innerIndent", "rightIndent"], () => { this.resetIndents(); });
this.registerPropertyChangedHandlers(["colSpan"], () => { this.parent?.updateColumns(); });
}
public getType(): string {
@@ -2083,15 +2080,6 @@ export class PanelModel extends PanelModelBase implements IElement {
}
return super.getSurvey(live);
}
- onSurveyLoad() {
- super.onSurveyLoad();
- this.onIndentChanged();
- }
- protected onSetData() {
- super.onSetData();
- this.onIndentChanged();
- this.calcHasTextInTitle();
- }
public get isPanel(): boolean {
return true;
}
@@ -2269,24 +2257,33 @@ export class PanelModel extends PanelModelBase implements IElement {
this.setPropertyValue("allowAdaptiveActions", val);
}
get innerPaddingLeft(): string {
- return this.getPropertyValue("innerPaddingLeft", "");
+ const func = (): string => {
+ return this.getIndentSize(this.innerIndent);
+ };
+ return this.getPropertyValue("innerPaddingLeft", undefined, func);
}
set innerPaddingLeft(val: string) {
this.setPropertyValue("innerPaddingLeft", val);
}
- private onIndentChanged() {
- if (!this.getSurvey()) return;
- this.innerPaddingLeft = this.getIndentSize(this.innerIndent);
- this.paddingLeft = this.getIndentSize(this.indent);
- this.paddingRight = this.getIndentSize(this.rightIndent);
+ protected calcPaddingLeft(): string {
+ return this.getIndentSize(this.indent);
}
+ protected calcPaddingRight(): string {
+ return this.getIndentSize(this.rightIndent);
+ }
+ protected resetIndents(): void {
+ this.resetPropertyValue("innerPaddingLeft");
+ super.resetIndents();
+ }
+
private getIndentSize(indent: number): string {
+ if(!this.survey) return undefined;
if (indent < 1) return "";
var css = (this).survey["css"];
- if (!css || !css.question.indent) return "";
+ if (!css || !css.question || !css.question.indent) return "";
return indent * css.question.indent + "px";
}
- public clearOnDeletingContainer() {
+ public clearOnDeletingContainer(): void {
this.elements.forEach((element) => {
if (element instanceof Question || element instanceof PanelModel) {
element.clearOnDeletingContainer();
diff --git a/packages/survey-core/src/question.ts b/packages/survey-core/src/question.ts
index b364d10062..43b806d610 100644
--- a/packages/survey-core/src/question.ts
+++ b/packages/survey-core/src/question.ts
@@ -91,27 +91,7 @@ export class Question extends SurveyElement
private isReadyValue: boolean = true;
private commentElements: Array;
private dependedQuestions: Array = [];
- public commentTextAreaModel: TextAreaModel;
-
- private getCommentTextAreaOptions(): ITextArea {
- const options: ITextArea = {
- question: this,
- id: () => this.commentId,
- propertyName: "comment",
- className: () => this.cssClasses.comment,
- placeholder: () => this.renderedCommentPlaceholder,
- isDisabledAttr: () => this.isInputReadOnly || false,
- rows: () => this.commentAreaRows,
- autoGrow: () => this.autoGrowComment,
- maxLength: () => this.getOthersMaxLength(),
- ariaRequired: () => this.a11y_input_ariaRequired,
- ariaLabel: () => this.a11y_input_ariaLabel,
- getTextValue: () => { return this.comment; },
- onTextAreaChange: (e) => { this.onCommentChange(e); },
- onTextAreaInput: (e) => { this.onCommentInput(e); },
- };
- return options;
- }
+ private commentTextAreaModelValue: TextAreaModel;
/**
* An event that is raised when the question's ready state has changed (expressions are evaluated, choices are loaded from a web resource specified by the `choicesByUrl` property, etc.).
@@ -162,7 +142,6 @@ export class Question extends SurveyElement
this.createNewArray("validators", (validator: any) => {
validator.errorOwner = this;
});
- this.commentTextAreaModel = new TextAreaModel(this.getCommentTextAreaOptions());
this.addExpressionProperty("visibleIf", (obj: Base, res: any) => { this.visible = res === true; });
this.addExpressionProperty("enableIf", (obj: Base, res: any) => { this.readOnly = res === false; });
@@ -194,7 +173,7 @@ export class Question extends SurveyElement
this.registerPropertyChangedHandlers(
["indent", "rightIndent"],
() => {
- this.onIndentChanged();
+ this.resetIndents();
}
);
@@ -225,7 +204,31 @@ export class Question extends SurveyElement
this.locProcessedTitle.sharedData = locTitleValue;
return locTitleValue;
}
-
+ public get commentTextAreaModel(): TextAreaModel {
+ if(!this.commentTextAreaModelValue) {
+ this.commentTextAreaModelValue = new TextAreaModel(this.getCommentTextAreaOptions());
+ }
+ return this.commentTextAreaModelValue;
+ }
+ private getCommentTextAreaOptions(): ITextArea {
+ const options: ITextArea = {
+ question: this,
+ id: () => this.commentId,
+ propertyName: "comment",
+ className: () => this.cssClasses.comment,
+ placeholder: () => this.renderedCommentPlaceholder,
+ isDisabledAttr: () => this.isInputReadOnly || false,
+ rows: () => this.commentAreaRows,
+ autoGrow: () => this.autoGrowComment,
+ maxLength: () => this.getOthersMaxLength(),
+ ariaRequired: () => this.a11y_input_ariaRequired,
+ ariaLabel: () => this.a11y_input_ariaLabel,
+ getTextValue: () => { return this.comment; },
+ onTextAreaChange: (e) => { this.onCommentChange(e); },
+ onTextAreaInput: (e) => { this.onCommentInput(e); },
+ };
+ return options;
+ }
public getSurvey(live: boolean = false): ISurvey {
if (live) {
return !!this.parent ? ( (this.parent)).getSurvey(live) : null;
@@ -645,7 +648,6 @@ export class Question extends SurveyElement
if (isLight !== true) {
this.runConditions();
}
- this.calcRenderedCommentPlaceholder();
if (!this.visible) {
this.updateIsVisibleProp();
}
@@ -661,7 +663,9 @@ export class Question extends SurveyElement
if (this.parent === val) return;
this.removeFromParent();
this.setPropertyValue("parent", val);
- this.updateQuestionCss();
+ if(!!val) {
+ this.updateQuestionCss();
+ }
this.onParentChanged();
}
protected onParentChanged(): void { }
@@ -897,7 +901,7 @@ export class Question extends SurveyElement
* @see comment
* @see commentText
*/
- @property({ localizable: true, onSet: (val, target) => target.calcRenderedCommentPlaceholder() }) commentPlaceholder: string;
+ @property({ localizable: true, onSet: (val, target) => target.resetRenderedCommentPlaceholder() }) commentPlaceholder: string;
public get commentPlaceHolder(): string {
return this.commentPlaceholder;
@@ -906,11 +910,13 @@ export class Question extends SurveyElement
this.commentPlaceholder = newValue;
}
public get renderedCommentPlaceholder(): string {
- return this.getPropertyValue("renderedCommentPlaceholder");
+ const func = (): any => {
+ return !this.isReadOnly ? this.commentPlaceHolder : undefined;
+ };
+ return this.getPropertyValue("renderedCommentPlaceholder", undefined, func);
}
- private calcRenderedCommentPlaceholder() {
- const res = !this.isReadOnly ? this.commentPlaceHolder : undefined;
- this.setPropertyValue("renderedCommentPlaceholder", res);
+ private resetRenderedCommentPlaceholder() {
+ this.resetPropertyValue("renderedCommentPlaceholder");
}
public getAllErrors(): Array {
return this.errors.slice();
@@ -933,7 +939,7 @@ export class Question extends SurveyElement
}
public localeChanged(): void {
super.localeChanged();
- this.calcRenderedCommentPlaceholder();
+ this.resetRenderedCommentPlaceholder();
if (!!this.localeChangedCallback) {
this.localeChangedCallback();
}
@@ -1181,7 +1187,7 @@ export class Question extends SurveyElement
if (reNew) {
this.updateQuestionCss(true);
}
- this.onIndentChanged();
+ this.resetIndents();
}
protected updateQuestionCss(reNew?: boolean): void {
if (
@@ -1233,9 +1239,11 @@ export class Question extends SurveyElement
public get renderCssRoot(): string {
return this.cssClasses.root || undefined;
}
- private onIndentChanged() {
- this.paddingLeft = this.getIndentSize(this.indent);
- this.paddingRight = this.getIndentSize(this.rightIndent);
+ protected calcPaddingLeft(): string {
+ return this.getIndentSize(this.indent);
+ }
+ protected calcPaddingRight(): string {
+ return this.getIndentSize(this.rightIndent);
}
private getIndentSize(indent: number): string {
if (indent < 1 || !this.getSurvey() || !this.cssClasses || !this.cssClasses.indent) return "";
@@ -1463,7 +1471,7 @@ export class Question extends SurveyElement
this.clearErrors();
}
this.updateQuestionCss();
- this.calcRenderedCommentPlaceholder();
+ this.resetRenderedCommentPlaceholder();
}
/**
* A Boolean expression. If it evaluates to `false`, this question becomes read-only.
@@ -1532,14 +1540,10 @@ export class Question extends SurveyElement
if (this.isEmpty()) {
this.initDataFromSurvey();
}
- this.calcRenderedCommentPlaceholder();
- this.onIndentChanged();
}
protected onSetData(): void {
super.onSetData();
- if (!this.survey) return;
- this.onIndentChanged();
- if(!this.isDesignMode) {
+ if(!this.isDesignMode && !!this.survey && !this.isLoadingFromJson) {
this.initDataFromSurvey();
this.onSurveyValueChanged(this.value);
this.updateValueWithDefaults();
@@ -1705,7 +1709,6 @@ export class Question extends SurveyElement
protected clearValueIfInvisibleCore(reason: string): void {
if (this.canClearValueAsInvisible(reason)) {
this.clearValue();
- this.setValueChangedDirectly(undefined);
}
}
/**
diff --git a/packages/survey-core/src/question_baseselect.ts b/packages/survey-core/src/question_baseselect.ts
index e8586a9ab3..280acfa8f3 100644
--- a/packages/survey-core/src/question_baseselect.ts
+++ b/packages/survey-core/src/question_baseselect.ts
@@ -14,7 +14,7 @@ import { settings } from "./settings";
import { SurveyElement } from "./survey-element";
import { CssClassBuilder } from "./utils/cssClassBuilder";
import { ITextArea, TextAreaModel } from "./utils/text-area";
-import { cleanHtmlElementAfterAnimation, mergeValues, prepareElementForVerticalAnimation, setPropertiesOnElementForAnimation } from "./utils/utils";
+import { cleanHtmlElementAfterAnimation, prepareElementForVerticalAnimation, setPropertiesOnElementForAnimation } from "./utils/utils";
import { AnimationGroup, IAnimationGroupConsumer } from "./utils/animation";
/**
@@ -23,8 +23,8 @@ import { AnimationGroup, IAnimationGroupConsumer } from "./utils/animation";
export class QuestionSelectBase extends Question {
public visibleChoicesChangedCallback: () => void;
public loadedChoicesFromServerCallback: () => void;
- public otherTextAreaModel: TextAreaModel;
public renderedChoicesChangedCallback: () => void;
+ private otherTextAreaModelValue: TextAreaModel;
private filteredChoicesValue: Array;
private conditionChoicesVisibleIfRunner: ConditionRunner;
private conditionChoicesEnableIfRunner: ConditionRunner;
@@ -49,26 +49,6 @@ export class QuestionSelectBase extends Question {
}
}) protected selectedItemValues: any;
- private getOtherTextAreaOptions(): ITextArea {
- const options: ITextArea = {
- question: this,
- id: () => this.otherId,
- propertyName: "otherValue",
- className: () => this.cssClasses.other,
- placeholder: () => this.otherPlaceholder,
- isDisabledAttr: () => this.isInputReadOnly || false,
- rows: () => this.commentAreaRows,
- maxLength: () => this.getOthersMaxLength(),
- autoGrow: () => this.survey && this.survey.autoGrowComment,
- ariaRequired: () => this.ariaRequired || this.a11y_input_ariaRequired,
- ariaLabel: () => this.ariaLabel || this.a11y_input_ariaLabel,
- getTextValue: () => { return this.otherValue; },
- onTextAreaChange: (e) => { this.onOtherValueChange(e); },
- onTextAreaInput: (e) => { this.onOtherValueInput(e); },
- };
- return options;
- }
-
constructor(name: string) {
super(name);
this.noneItemValue = this.createDefaultItem(settings.noneItemValue, "noneText", "noneItemText");
@@ -90,7 +70,6 @@ export class QuestionSelectBase extends Question {
this.registerPropertyChangedHandlers(["hideIfChoicesEmpty"], () => {
this.onVisibleChanged();
});
- this.otherTextAreaModel = new TextAreaModel(this.getOtherTextAreaOptions());
this.createNewArray("visibleChoices", () => this.updateRenderedChoices(), () => this.updateRenderedChoices());
this.setNewRestfulProperty();
var locOtherText = this.createLocalizableString("otherText", this.otherItemValue, true, "otherItemText");
@@ -126,6 +105,31 @@ export class QuestionSelectBase extends Question {
q.removeDependedQuestion(this);
}
}
+ public get otherTextAreaModel(): TextAreaModel {
+ if(!this.otherTextAreaModelValue) {
+ this.otherTextAreaModelValue = new TextAreaModel(this.getOtherTextAreaOptions());
+ }
+ return this.otherTextAreaModelValue;
+ }
+ private getOtherTextAreaOptions(): ITextArea {
+ const options: ITextArea = {
+ question: this,
+ id: () => this.otherId,
+ propertyName: "otherValue",
+ className: () => this.cssClasses.other,
+ placeholder: () => this.otherPlaceholder,
+ isDisabledAttr: () => this.isInputReadOnly || false,
+ rows: () => this.commentAreaRows,
+ maxLength: () => this.getOthersMaxLength(),
+ autoGrow: () => this.survey && this.survey.autoGrowComment,
+ ariaRequired: () => this.ariaRequired || this.a11y_input_ariaRequired,
+ ariaLabel: () => this.ariaLabel || this.a11y_input_ariaLabel,
+ getTextValue: () => { return this.otherValue; },
+ onTextAreaChange: (e) => { this.onOtherValueChange(e); },
+ onTextAreaInput: (e) => { this.onOtherValueInput(e); },
+ };
+ return options;
+ }
protected resetDependedQuestion(): void {
this.choicesFromQuestion = "";
}
@@ -2019,7 +2023,7 @@ export class QuestionSelectBase extends Question {
public get questionName() {
return this.name + "_" + this.id;
}
- public getItemEnabled(item: ItemValue) {
+ public getItemEnabled(item: ItemValue): boolean {
return !this.isDisabledAttr && item.isEnabled;
}
private focusOtherComment() {
@@ -2049,22 +2053,6 @@ export class QuestionSelectBase extends Question {
public set itemComponent(value: string) {
this.setPropertyValue("itemComponent", value);
}
- protected updateCssClasses(res: any, css: any) {
- super.updateCssClasses(res, css);
- if (!!this.dropdownListModel) {
- const listCssClasses = {};
- mergeValues(css.list, listCssClasses);
- mergeValues(res.list, listCssClasses);
- res["list"] = listCssClasses;
- }
- }
- protected calcCssClasses(css: any): any {
- const classes = super.calcCssClasses(css);
- if (this.dropdownListModel) {
- this.dropdownListModel.updateCssClasses(classes.popup, classes.list);
- }
- return classes;
- }
}
/**
* A base class for multiple-selection question types that can display choice items in multiple columns ([Checkbox](https://surveyjs.io/form-library/documentation/questioncheckboxmodel), [Radiogroup](https://surveyjs.io/form-library/documentation/questionradiogroupmodel), [Image Picker](https://surveyjs.io/form-library/documentation/questionimagepickermodel)).
diff --git a/packages/survey-core/src/question_comment.ts b/packages/survey-core/src/question_comment.ts
index 9213a1a125..77cdfe79dd 100644
--- a/packages/survey-core/src/question_comment.ts
+++ b/packages/survey-core/src/question_comment.ts
@@ -12,8 +12,17 @@ import { Helpers } from "./helpers";
*/
export class QuestionCommentModel extends QuestionTextBase {
private element: HTMLElement;
- public textAreaModel: TextAreaModel;
+ private textAreaModelValue: TextAreaModel;
+ constructor(name: string) {
+ super(name);
+ }
+ public get textAreaModel(): TextAreaModel {
+ if(!this.textAreaModelValue) {
+ this.textAreaModelValue = new TextAreaModel(this.getTextAreaOptions());
+ }
+ return this.textAreaModelValue;
+ }
private getTextAreaOptions(): ITextArea {
const _this = this;
const updateQuestionValue = (newValue: any) => {
@@ -49,12 +58,6 @@ export class QuestionCommentModel extends QuestionTextBase {
};
return options;
}
-
- constructor(name: string) {
- super(name);
-
- this.textAreaModel = new TextAreaModel(this.getTextAreaOptions());
- }
/**
* Specifies the visible height of the comment area, measured in lines.
*
diff --git a/packages/survey-core/src/question_custom.ts b/packages/survey-core/src/question_custom.ts
index 77d9849bad..dddb4e5966 100644
--- a/packages/survey-core/src/question_custom.ts
+++ b/packages/survey-core/src/question_custom.ts
@@ -570,12 +570,12 @@ export abstract class QuestionCustomModelBase extends Question
);
}
}
- public onFirstRendering() {
+ protected onFirstRenderingCore(): void {
+ super.onFirstRenderingCore();
const el = this.getElement();
if (!!el) {
el.onFirstRendering();
}
- super.onFirstRendering();
}
public onHidingContent(): void {
super.onHidingContent();
diff --git a/packages/survey-core/src/question_dropdown.ts b/packages/survey-core/src/question_dropdown.ts
index 4a492178f5..41bd7013de 100644
--- a/packages/survey-core/src/question_dropdown.ts
+++ b/packages/survey-core/src/question_dropdown.ts
@@ -8,6 +8,7 @@ import { PopupModel } from "./popup";
import { EventBase } from "./base";
import { DropdownListModel } from "./dropdownListModel";
import { settings } from "./settings";
+import { updateListCssValues } from "./utils/utils";
/**
* A class that describes the Dropdown question type.
@@ -34,6 +35,7 @@ export class QuestionDropdownModel extends QuestionSelectBase {
constructor(name: string) {
super(name);
+ this.ariaExpanded = "false";
this.createLocalizableString("placeholder", this, false, true);
this.createLocalizableString("clearCaption", this, false, true);
this.registerPropertyChangedHandlers(["choicesMin", "choicesMax", "choicesStep"], () => {
@@ -201,7 +203,7 @@ export class QuestionDropdownModel extends QuestionSelectBase {
*/
@property({
onSet: (newValue: boolean, target: QuestionDropdownModel) => {
- if (!!target.dropdownListModel) {
+ if (!!target.dropdownListModelValue) {
target.dropdownListModel.setSearchEnabled(newValue);
}
}
@@ -235,7 +237,7 @@ export class QuestionDropdownModel extends QuestionSelectBase {
*/
@property({
onSet: (newValue: boolean, target: QuestionDropdownModel) => {
- if (!!target.dropdownListModel) {
+ if (!!target.dropdownListModelValue) {
target.dropdownListModel.setChoicesLazyLoadEnabled(newValue);
}
}
@@ -257,6 +259,19 @@ export class QuestionDropdownModel extends QuestionSelectBase {
.append(this.cssClasses.controlInputFieldComponent, !!this.inputFieldComponentName)
.toString();
}
+ protected updateCssClasses(res: any, css: any): void {
+ super.updateCssClasses(res, css);
+ if (this.useDropdownList) {
+ updateListCssValues(res, css);
+ }
+ }
+ protected calcCssClasses(css: any): any {
+ const classes = super.calcCssClasses(css);
+ if (this.dropdownListModelValue) {
+ this.dropdownListModel.updateCssClasses(classes.popup, classes.list);
+ }
+ return classes;
+ }
@property() suggestedItem: ItemValue;
public get selectedItemLocText() {
@@ -276,8 +291,9 @@ export class QuestionDropdownModel extends QuestionSelectBase {
const item = this.selectedItem;
return !!item ? item.text : "";
}
+ private get useDropdownList(): boolean { return this.renderAs !== "select"; }
public get dropdownListModel(): DropdownListModel {
- if (this.renderAs !== "select" && !this.dropdownListModelValue) {
+ if (this.useDropdownList && !this.dropdownListModelValue) {
this.dropdownListModelValue = new DropdownListModel(this);
}
return this.dropdownListModelValue;
@@ -286,7 +302,7 @@ export class QuestionDropdownModel extends QuestionSelectBase {
this.dropdownListModelValue = val;
}
public get popupModel(): PopupModel {
- return this.dropdownListModel?.popupModel;
+ return this.dropdownListModel.popupModel;
}
public onOpened: EventBase = this.addEvent();
@@ -294,7 +310,7 @@ export class QuestionDropdownModel extends QuestionSelectBase {
this.onOpened.fire(this, { question: this, choices: this.choices });
}
protected onSelectedItemValuesChangedHandler(newValue: any): void {
- this.dropdownListModel?.setInputStringFromSelectedItem(newValue);
+ this.dropdownListModelValue?.setInputStringFromSelectedItem(newValue);
super.onSelectedItemValuesChangedHandler(newValue);
}
protected hasUnknownValue(
@@ -321,7 +337,7 @@ export class QuestionDropdownModel extends QuestionSelectBase {
protected onVisibleChoicesChanged(): void {
super.onVisibleChoicesChanged();
- if (!this.isLoadingFromJson && this.popupModel) {
+ if (!!this.dropdownListModelValue) {
this.dropdownListModel.updateItems();
}
}
@@ -335,7 +351,7 @@ export class QuestionDropdownModel extends QuestionSelectBase {
public clearValue(keepComment?: boolean): void {
super.clearValue(keepComment);
this.lastSelectedItemValue = null;
- this.dropdownListModel?.clear();
+ this.dropdownListModelValue?.clear();
}
public afterRenderCore(el: any): void {
@@ -359,11 +375,11 @@ export class QuestionDropdownModel extends QuestionSelectBase {
}
protected supportEmptyValidation(): boolean { return true; }
protected onBlurCore(event: any): void {
- this.dropdownListModel?.onBlur(event);
+ this.dropdownListModel.onBlur(event);
super.onBlurCore(event);
}
protected onFocusCore(event: any): void {
- this.dropdownListModel?.onFocus(event);
+ this.dropdownListModel.onFocus(event);
super.onFocusCore(event);
}
@@ -371,6 +387,7 @@ export class QuestionDropdownModel extends QuestionSelectBase {
super.dispose();
if(!!this.dropdownListModelValue) {
this.dropdownListModelValue.dispose();
+ this.dropdownListModelValue = undefined;
}
}
}
diff --git a/packages/survey-core/src/question_file.ts b/packages/survey-core/src/question_file.ts
index c37177a3b1..aeea73108a 100644
--- a/packages/survey-core/src/question_file.ts
+++ b/packages/survey-core/src/question_file.ts
@@ -654,7 +654,7 @@ export class QuestionFileModel extends QuestionFileModelBase {
this.setPropertyValue("isPlayingVideo", show);
}
private updateCurrentMode(): void {
- if (!this.isDesignMode) {
+ if (!this.isDesignMode && this.survey) {
if (this.sourceType !== "file") {
this.camera.hasCamera((res: boolean) => {
this.setPropertyValue("currentMode", res && this.isDefaultV2Theme ? this.sourceType : "file");
@@ -830,9 +830,12 @@ export class QuestionFileModel extends QuestionFileModelBase {
public canPreviewImage(fileItem: any): boolean {
return this.allowImagesPreview && !!fileItem && this.isFileImage(fileItem);
}
+ private prevLoadedPreviewValue: any;
protected loadPreview(newValue: any): void {
+ if (this.showPreview && this.prevLoadedPreviewValue === newValue) return;
this.previewValue.splice(0, this.previewValue.length);
if (!this.showPreview || !newValue) return;
+ this.prevLoadedPreviewValue = newValue;
var newValues = Array.isArray(newValue)
? newValue
: !!newValue
diff --git a/packages/survey-core/src/question_paneldynamic.ts b/packages/survey-core/src/question_paneldynamic.ts
index 53a72aac28..30b03b5f3d 100644
--- a/packages/survey-core/src/question_paneldynamic.ts
+++ b/packages/survey-core/src/question_paneldynamic.ts
@@ -1837,21 +1837,21 @@ export class QuestionPanelDynamicModel extends Question
private get canBuildPanels(): boolean {
return !this.isLoadingFromJson && !this.useTemplatePanel;
}
- public onFirstRendering(): void {
- super.onFirstRendering();
+ protected onFirstRenderingCore(): void {
+ super.onFirstRenderingCore();
this.buildPanelsFirstTime();
this.template.onFirstRendering();
for (var i = 0; i < this.panelsCore.length; i++) {
this.panelsCore[i].onFirstRendering();
}
}
- public localeChanged() {
+ public localeChanged(): void {
super.localeChanged();
for (var i = 0; i < this.panelsCore.length; i++) {
this.panelsCore[i].localeChanged();
}
}
- public runCondition(values: HashTable, properties: HashTable) {
+ public runCondition(values: HashTable, properties: HashTable): void {
super.runCondition(values, properties);
this.runPanelsCondition(this.panelsCore, values, properties);
}
@@ -2119,12 +2119,14 @@ export class QuestionPanelDynamicModel extends Question
if (!this.isDesignMode && !this.isReadOnly && !this.isValueEmpty(panel.getValue())) {
this.runPanelsCondition([panel], this.getDataFilteredValues(), this.getDataFilteredProperties());
}
- panel.onFirstRendering();
var questions = panel.questions;
for (var i = 0; i < questions.length; i++) {
questions[i].setParentQuestion(this);
}
- panel.locStrsChanged();
+ if(this.wasRendered) {
+ panel.onFirstRendering();
+ panel.locStrsChanged();
+ }
panel.onGetFooterActionsCallback = () => {
return this.getPanelActions(panel);
};
@@ -2216,7 +2218,7 @@ export class QuestionPanelDynamicModel extends Question
}
protected onSetData(): void {
super.onSetData();
- if (this.useTemplatePanel) {
+ if(!this.isLoadingFromJson && this.useTemplatePanel) {
this.setTemplatePanelSurveyImpl();
this.rebuildPanels();
}
diff --git a/packages/survey-core/src/question_rating.ts b/packages/survey-core/src/question_rating.ts
index 09e7c3e4d3..983b021f84 100644
--- a/packages/survey-core/src/question_rating.ts
+++ b/packages/survey-core/src/question_rating.ts
@@ -7,7 +7,7 @@ import { settings } from "./settings";
import { getLocaleString } from "./surveyStrings";
import { CssClassBuilder } from "./utils/cssClassBuilder";
import { Base } from "./base";
-import { mergeValues } from "./utils/utils";
+import { updateListCssValues } from "./utils/utils";
import { DropdownListModel } from "./dropdownListModel";
import { SurveyModel } from "./survey";
import { ISurveyImpl } from "./base-interfaces";
@@ -869,7 +869,8 @@ export class QuestionRatingModel extends Question {
}
protected onBeforeSetCompactRenderer(): void {
if (!this.dropdownListModelValue) {
- this.dropdownListModel = new DropdownListModel(this);
+ this.dropdownListModelValue = new DropdownListModel(this);
+ this.ariaExpanded = "false";
}
}
protected getCompactRenderAs(): string {
@@ -882,6 +883,7 @@ export class QuestionRatingModel extends Question {
private dropdownListModelValue: DropdownListModel;
public set dropdownListModel(val: DropdownListModel) {
this.dropdownListModelValue = val;
+ this.ariaExpanded = !!val ? "false" : undefined;
this.updateElementCss();
}
public get dropdownListModel(): DropdownListModel {
@@ -896,17 +898,12 @@ export class QuestionRatingModel extends Question {
}
protected updateCssClasses(res: any, css: any) {
super.updateCssClasses(res, css);
- if(!!this.dropdownListModel) {
- const listCssClasses = {};
- mergeValues(css.list, listCssClasses);
- mergeValues(res.list, listCssClasses);
- res["list"] = listCssClasses;
- }
+ updateListCssValues(res, css);
}
protected calcCssClasses(css: any): any {
const classes = super.calcCssClasses(css);
- if(this.dropdownListModel) {
- this.dropdownListModel.updateCssClasses(classes.popup, classes.list);
+ if(this.dropdownListModelValue) {
+ this.dropdownListModelValue.updateCssClasses(classes.popup, classes.list);
}
return classes;
}
@@ -925,6 +922,7 @@ export class QuestionRatingModel extends Question {
super.dispose();
if(!!this.dropdownListModelValue) {
this.dropdownListModelValue.dispose();
+ this.dropdownListModelValue = undefined;
}
}
}
diff --git a/packages/survey-core/src/question_tagbox.ts b/packages/survey-core/src/question_tagbox.ts
index 7c5d7535bc..6be64a809b 100644
--- a/packages/survey-core/src/question_tagbox.ts
+++ b/packages/survey-core/src/question_tagbox.ts
@@ -8,6 +8,7 @@ import { DropdownMultiSelectListModel } from "./dropdownMultiSelectListModel";
import { EventBase } from "./base";
import { settings } from "./settings";
import { ItemValue } from "./itemvalue";
+import { updateListCssValues } from "./utils/utils";
/**
* A class that describes the Multi-Select Dropdown (Tag Box) question type.
@@ -15,12 +16,13 @@ import { ItemValue } from "./itemvalue";
* [View Demo](https://surveyjs.io/form-library/examples/how-to-create-multiselect-tag-box/ (linkStyle))
*/
export class QuestionTagboxModel extends QuestionCheckboxModel {
- dropdownListModel: DropdownMultiSelectListModel;
+ private dropdownListModelValue: DropdownMultiSelectListModel;
private itemDisplayNameMap: { [key: string]: string} = {};
private deselectAllItemText: LocalizableString;
constructor(name: string) {
super(name);
+ this.ariaExpanded = "false";
this.createLocalizableString("placeholder", this, false, true);
this.createLocalizableString("clearCaption", this, false, true);
this.createLocalizableString("readOnlyText", this, true);
@@ -33,7 +35,7 @@ export class QuestionTagboxModel extends QuestionCheckboxModel {
public locStrsChanged(): void {
super.locStrsChanged();
this.updateReadOnlyText();
- this.dropdownListModel?.locStrsChanged();
+ this.dropdownListModelValue?.locStrsChanged();
}
private updateReadOnlyText(): void {
this.readOnlyText = this.displayValue || this.placeholder;
@@ -41,18 +43,14 @@ export class QuestionTagboxModel extends QuestionCheckboxModel {
protected getDefaultItemComponent(): string {
return "";
}
- public onSurveyLoad(): void {
- super.onSurveyLoad();
- this.createDropdownListModel();
- }
- protected onSetData(): void {
- super.onSetData();
- this.createDropdownListModel();
- }
- private createDropdownListModel(): void {
- if (!this.dropdownListModel && !this.isLoadingFromJson) {
- this.dropdownListModel = new DropdownMultiSelectListModel(this);
+ public get dropdownListModel(): DropdownMultiSelectListModel {
+ if (!this.dropdownListModelValue) {
+ this.dropdownListModelValue = new DropdownMultiSelectListModel(this);
}
+ return this.dropdownListModelValue;
+ }
+ public set dropdownListModel(val: DropdownMultiSelectListModel) {
+ this.dropdownListModelValue = val;
}
/**
* Specifies a comparison operation used to filter the drop-down list. Applies only if [`searchEnabled`](#searchEnabled) is `true`.
@@ -74,7 +72,7 @@ export class QuestionTagboxModel extends QuestionCheckboxModel {
*/
@property({
onSet: (newValue: boolean, target: QuestionTagboxModel) => {
- if (!!target.dropdownListModel) {
+ if (!!target.dropdownListModelValue) {
target.dropdownListModel.setSearchEnabled(newValue);
}
}
@@ -85,7 +83,7 @@ export class QuestionTagboxModel extends QuestionCheckboxModel {
*/
@property({
onSet: (newValue: boolean, target: QuestionTagboxModel) => {
- if (!!target.dropdownListModel) {
+ if (!!target.dropdownListModelValue) {
target.dropdownListModel.setHideSelectedItems(newValue);
}
}
@@ -98,7 +96,7 @@ export class QuestionTagboxModel extends QuestionCheckboxModel {
*/
@property({
onSet: (newValue: boolean, target: QuestionTagboxModel) => {
- if (!!target.dropdownListModel) {
+ if (!!target.dropdownListModelValue) {
target.dropdownListModel.setChoicesLazyLoadEnabled(newValue);
}
}
@@ -156,7 +154,7 @@ export class QuestionTagboxModel extends QuestionCheckboxModel {
return "combobox";
}
public get popupModel(): PopupModel {
- return this.dropdownListModel?.popupModel;
+ return this.dropdownListModel.popupModel;
}
public getControlClass(): string {
@@ -170,6 +168,17 @@ export class QuestionTagboxModel extends QuestionCheckboxModel {
.append(this.cssClasses.controlPreview, this.isPreviewStyle)
.toString();
}
+ protected updateCssClasses(res: any, css: any): void {
+ super.updateCssClasses(res, css);
+ updateListCssValues(res, css);
+ }
+ protected calcCssClasses(css: any): any {
+ const classes = super.calcCssClasses(css);
+ if (this.dropdownListModelValue) {
+ this.dropdownListModel.updateCssClasses(classes.popup, classes.list);
+ }
+ return classes;
+ }
public onOpened: EventBase = this.addEvent();
public onOpenedCallBack(): void {
this.onOpened.fire(this, { question: this, choices: this.choices });
@@ -188,7 +197,7 @@ export class QuestionTagboxModel extends QuestionCheckboxModel {
protected onVisibleChoicesChanged(): void {
super.onVisibleChoicesChanged();
- if (this.popupModel) {
+ if (!!this.dropdownListModelValue) {
this.dropdownListModel.updateItems();
}
}
@@ -236,11 +245,11 @@ export class QuestionTagboxModel extends QuestionCheckboxModel {
}
protected supportEmptyValidation(): boolean { return true; }
protected onBlurCore(event: any): void {
- this.dropdownListModel?.onBlur(event);
+ this.dropdownListModel.onBlur(event);
super.onBlurCore(event);
}
protected onFocusCore(event: any): void {
- this.dropdownListModel?.onFocus(event);
+ this.dropdownListModel.onFocus(event);
super.onFocusCore(event);
}
protected allElementsSelected(): boolean {
@@ -255,13 +264,14 @@ export class QuestionTagboxModel extends QuestionCheckboxModel {
public dispose(): void {
super.dispose();
- if(!!this.dropdownListModel) {
- this.dropdownListModel.dispose();
+ if(!!this.dropdownListModelValue) {
+ this.dropdownListModelValue.dispose();
+ this.dropdownListModelValue = undefined;
}
}
public clearValue(keepComment?: boolean): void {
super.clearValue(keepComment);
- this.dropdownListModel?.clear();
+ this.dropdownListModelValue?.clear();
}
public get showClearButton(): boolean {
return this.allowClear && !this.isEmpty() && (!this.isDesignMode || settings.supportCreatorV2);
diff --git a/packages/survey-core/src/question_text.ts b/packages/survey-core/src/question_text.ts
index 0eb1145ad4..aab4b7028e 100644
--- a/packages/survey-core/src/question_text.ts
+++ b/packages/survey-core/src/question_text.ts
@@ -134,7 +134,7 @@ export class QuestionTextModel extends QuestionTextBase {
);
this.registerPropertyChangedHandlers(["inputType", "size"], () => {
this.updateInputSize();
- this.calcRenderedPlaceholder();
+ this.resetRenderedPlaceholder();
});
}
protected isTextValue(): boolean {
diff --git a/packages/survey-core/src/question_textbase.ts b/packages/survey-core/src/question_textbase.ts
index bdbaf29c87..65c61ed8ee 100644
--- a/packages/survey-core/src/question_textbase.ts
+++ b/packages/survey-core/src/question_textbase.ts
@@ -46,7 +46,7 @@ export class QuestionTextBase extends Question {
/**
* A placeholder for the input field.
*/
- @property({ localizable: true, onSet: (val, target) => target.calcRenderedPlaceholder() })
+ @property({ localizable: true, onSet: (val, target) => target.resetRenderedPlaceholder() })
public placeholder: string;
public get placeHolder(): string { return this.placeholder; }
public set placeHolder(val: string) { this.placeholder = val; }
@@ -80,34 +80,22 @@ export class QuestionTextBase extends Question {
return this.textUpdateMode == "onTyping";
}
public get renderedPlaceholder(): string {
- return this.getPropertyValue("renderedPlaceholder");
- }
- protected setRenderedPlaceholder(val: string): void {
- this.setPropertyValue("renderedPlaceholder", val);
+ const func = (): string => {
+ return this.hasPlaceholder() ? this.placeHolder : undefined;
+ };
+ return this.getPropertyValue("renderedPlaceholder", undefined, func);
}
protected onReadOnlyChanged(): void {
super.onReadOnlyChanged();
- this.calcRenderedPlaceholder();
- }
- public onSurveyLoad(): void {
- this.calcRenderedPlaceholder();
- super.onSurveyLoad();
+ this.resetRenderedPlaceholder();
}
public localeChanged(): void {
super.localeChanged();
- this.calcRenderedPlaceholder();
- }
- public setSurveyImpl(value: ISurveyImpl, isLight?: boolean): void {
- super.setSurveyImpl(value, isLight);
- this.calcRenderedPlaceholder();
+ this.resetRenderedPlaceholder();
}
protected supportEmptyValidation(): boolean { return true; }
- protected calcRenderedPlaceholder() {
- let res = this.placeHolder;
- if(!!res && !this.hasPlaceholder()) {
- res = undefined;
- }
- this.setRenderedPlaceholder(res);
+ protected resetRenderedPlaceholder(): void {
+ this.resetPropertyValue("renderedPlaceholder");
}
protected hasPlaceholder(): boolean {
return !this.isReadOnly;
diff --git a/packages/survey-core/src/survey-element.ts b/packages/survey-core/src/survey-element.ts
index d23de3961d..092fe3d23f 100644
--- a/packages/survey-core/src/survey-element.ts
+++ b/packages/survey-core/src/survey-element.ts
@@ -522,7 +522,7 @@ export class SurveyElement extends SurveyElementCore implements ISurvey
this.textProcessorValue = this.surveyImplValue.getTextProcessor();
this.onSetData();
}
- if (!!this.survey) {
+ if (!!this.survey/* && !this.isLoadingFromJson*/) {
this.updateDescriptionVisibility(this.description);
this.clearCssClasses();
}
@@ -625,18 +625,27 @@ export class SurveyElement extends SurveyElementCore implements ISurvey
private get css(): any {
return !!this.survey ? this.survey.getCss() : {};
}
+ private isCssValueCalculating: boolean;
public get cssClassesValue(): any {
- return this.getPropertyValueWithoutDefault("cssClassesValue");
- }
- public set cssClassesValue(val: any) {
- this.setPropertyValue("cssClassesValue", val);
+ let res = this.getPropertyValueWithoutDefault("cssClassesValue");
+ if(!res && !this.isCssValueCalculating) {
+ this.isCssValueCalculating = true;
+ res = this.createCssClassesValue();
+ this.isCssValueCalculating = false;
+ }
+ return res;
}
- private ensureCssClassesValue() {
+ private ensureCssClassesValue(): void {
if (!this.cssClassesValue) {
- this.cssClassesValue = this.calcCssClasses(this.css);
- this.updateElementCssCore(this.cssClassesValue);
+ this.createCssClassesValue();
}
}
+ private createCssClassesValue(): any {
+ const res = this.calcCssClasses(this.css);
+ this.setPropertyValue("cssClassesValue", res);
+ this.updateElementCssCore(this.cssClassesValue);
+ return res;
+ }
/**
* Returns an object in which keys are UI elements and values are CSS classes applied to them.
*
@@ -667,13 +676,13 @@ export class SurveyElement extends SurveyElementCore implements ISurvey
return this.cssClasses.titleExpandableSvg;
}
protected calcCssClasses(css: any): any { return undefined; }
- protected updateElementCssCore(cssClasses: any) { }
+ protected updateElementCssCore(cssClasses: any): void { }
public get cssError(): string { return ""; }
- public updateElementCss(reNew?: boolean) {
+ public updateElementCss(reNew?: boolean): void {
this.clearCssClasses();
}
- protected clearCssClasses() {
- this.cssClassesValue = undefined;
+ protected clearCssClasses(): void {
+ this.resetPropertyValue("cssClassesValue");
}
protected getIsLoadingFromJson(): boolean {
if (super.getIsLoadingFromJson()) return true;
@@ -751,7 +760,12 @@ export class SurveyElement extends SurveyElementCore implements ISurvey
private wasRenderedValue: boolean;
public get wasRendered(): boolean { return !!this.wasRenderedValue; }
public onFirstRendering(): void {
- this.wasRenderedValue = true;
+ if(!this.wasRendered) {
+ this.wasRenderedValue = true;
+ this.onFirstRenderingCore();
+ }
+ }
+ protected onFirstRenderingCore(): void {
this.ensureCssClassesValue();
}
endLoadingFromJson(): void {
@@ -829,7 +843,7 @@ export class SurveyElement extends SurveyElementCore implements ISurvey
if (!html || !this.textProcessor) return html;
return this.textProcessor.processText(html, true);
}
- protected onSetData() { }
+ protected onSetData(): void { }
public get parent(): IPanel {
return this.getPropertyValue("parent", null);
}
@@ -1006,16 +1020,20 @@ export class SurveyElement extends SurveyElementCore implements ISurvey
return style;
}
get paddingLeft(): string {
- return this.getPropertyValue("paddingLeft", "");
+ return this.getPropertyValue("paddingLeft", undefined, () => this.calcPaddingLeft());
}
- set paddingLeft(val: string) {
- this.setPropertyValue("paddingLeft", val);
+ protected calcPaddingLeft(): string {
+ return "";
}
get paddingRight(): string {
- return this.getPropertyValue("paddingRight", "");
+ return this.getPropertyValue("paddingRight", undefined, () => this.calcPaddingRight());
+ }
+ protected calcPaddingRight(): string {
+ return "";
}
- set paddingRight(val: string) {
- this.setPropertyValue("paddingRight", val);
+ protected resetIndents(): void {
+ this.resetPropertyValue("paddingLeft");
+ this.resetPropertyValue("paddingRight");
}
@property({ defaultValue: true }) allowRootStyle: boolean;
diff --git a/packages/survey-core/src/utils/utils.ts b/packages/survey-core/src/utils/utils.ts
index 4bfd103474..bacadb9d89 100644
--- a/packages/survey-core/src/utils/utils.ts
+++ b/packages/survey-core/src/utils/utils.ts
@@ -573,7 +573,7 @@ export function sanitizeEditableContent(element: any, cleanLineBreaks: boolean =
range.setStart(range.endContainer, range.endOffset);
}
}
-function mergeValues(src: any, dest: any) {
+function mergeValues(src: any, dest: any): void {
if (!dest || !src) return;
if (typeof dest !== "object") return;
for (var key in src) {
@@ -586,6 +586,12 @@ function mergeValues(src: any, dest: any) {
}
}
}
+function updateListCssValues(res: any, css: any): void {
+ const listCssClasses = {};
+ mergeValues(css.list, listCssClasses);
+ mergeValues(res.list, listCssClasses);
+ res["list"] = listCssClasses;
+}
export class Logger {
private _result = "";
@@ -803,6 +809,7 @@ export function roundTo2Decimals(number: number): number {
export {
mergeValues,
+ updateListCssValues,
getElementWidth,
isContainerVisible,
classesToSelector,
diff --git a/packages/survey-core/tests/dropdown_list_model_test.ts b/packages/survey-core/tests/dropdown_list_model_test.ts
index 36dc71fe79..927067f38d 100644
--- a/packages/survey-core/tests/dropdown_list_model_test.ts
+++ b/packages/survey-core/tests/dropdown_list_model_test.ts
@@ -281,7 +281,7 @@ QUnit.test("Check list classes with onUpdateQuestionCssClasses", function (asser
const question = survey.getAllQuestions()[0];
const dropdownListModel = question.dropdownListModel;
question.dropdownListModel = dropdownListModel;
- question.onFirstRendering();
+ assert.ok(question.cssClassesValue);
const list: ListModel = dropdownListModel.popupModel.contentComponentData.model as ListModel;
assert.equal(list.cssClasses.item, "original-class custom-class");
assert.equal(list.cssClasses.itemSelected, "original-class-selected custom-class-selected");
diff --git a/packages/survey-core/tests/listModelTests.ts b/packages/survey-core/tests/listModelTests.ts
index bbdfe2b953..1bd1ab1438 100644
--- a/packages/survey-core/tests/listModelTests.ts
+++ b/packages/survey-core/tests/listModelTests.ts
@@ -131,6 +131,23 @@ QUnit.test("ListModel custom onFilter", assert => {
ListModel.MINELEMENTCOUNT = oldValueMINELEMENTCOUNT;
});
+QUnit.test("ListModel: refresh & isEmpty", assert => {
+ const items = [
+ new Action({ id: "test1", title: "test1" }),
+ new Action({ id: "test2", title: "test2" })
+ ];
+ const myObject = new MyObject(items);
+ const list = new ListModel({ items: items, onSelectionChanged: () => { }, allowSelection: true } as any);
+ assert.equal(list.isEmpty, false, "#1");
+ list.actions[0].setVisible(false);
+ list.actions[1].setVisible(false);
+ list.refresh();
+ assert.equal(list.isEmpty, true, "#2");
+ list.actions[1].setVisible(true);
+ list.refresh();
+ assert.equal(list.isEmpty, false, "#3");
+});
+
QUnit.test("ListModel custom onFilter: item is not found when a search string contains a white space", assert => {
ListModel.MINELEMENTCOUNT = 5;
const items = [
diff --git a/packages/survey-core/tests/questionDropdownTests.ts b/packages/survey-core/tests/questionDropdownTests.ts
index d9b077df43..f9478655d9 100644
--- a/packages/survey-core/tests/questionDropdownTests.ts
+++ b/packages/survey-core/tests/questionDropdownTests.ts
@@ -9,6 +9,7 @@ import { PopupBaseViewModel } from "../src/popup-view-model";
import { PopupDropdownViewModel } from "../src/popup-dropdown-view-model";
import { PopupModalViewModel } from "../src/popup-modal-view-model";
import { QuestionMatrixDynamicModel } from "../src/question_matrixdynamic";
+import { IAction } from "../src/actions/action";
export default QUnit.module("Dropdown question");
@@ -1051,6 +1052,25 @@ QUnit.test("itemsSettings property", assert => {
done1();
}, onChoicesLazyLoadCallbackTimeOut + callbackTimeOutDelta);
});
+QUnit.test("rendering actions id", assert => {
+ const json = {
+ questions: [{
+ type: "dropdown",
+ name: "q1",
+ choices: ["Item1", "Item2"]
+ }]
+ };
+ const survey = new SurveyModel(json);
+ const question = survey.getQuestionByName("q1");
+ question.id = "el1";
+ const listModel = question.popupModel.contentComponentData.model as ListModel;
+ const actions = listModel.renderedActions;
+ assert.equal(actions.length, 2, "two actions");
+ assert.equal((actions[0]).elementId, "el1i_listItem1", "elementId, action1");
+ assert.equal((actions[1]).elementId, "el1i_listItem2", "elementId, action2");
+ assert.equal((actions[0]).disableTabStop, true, "disableTabStop, action1");
+ assert.equal((actions[1]).disableTabStop, true, "disableTabStop, action2");
+});
QUnit.test("Test dropdown choices change should update strings", function (assert) {
const json = {
diff --git a/packages/survey-core/tests/questionFileTests.ts b/packages/survey-core/tests/questionFileTests.ts
index 457b580d5a..342341e1c4 100644
--- a/packages/survey-core/tests/questionFileTests.ts
+++ b/packages/survey-core/tests/questionFileTests.ts
@@ -1416,10 +1416,10 @@ QUnit.test("QuestionFile current mode property, camera is not available", functi
{ type: "file", name: "q4", sourceType: "file-camera" },
]
});
- assert.equal(survey.getQuestionByName("q1").currentMode, "file");
- assert.equal(survey.getQuestionByName("q2").currentMode, "file");
- assert.equal(survey.getQuestionByName("q3").currentMode, "camera");
- assert.equal(survey.getQuestionByName("q4").currentMode, "file-camera");
+ assert.equal(survey.getQuestionByName("q1").currentMode, "file", "#1.1");
+ assert.equal(survey.getQuestionByName("q2").currentMode, "file", "#1.2");
+ assert.equal(survey.getQuestionByName("q3").currentMode, "camera", "#1.3");
+ assert.equal(survey.getQuestionByName("q4").currentMode, "file-camera", "#1.4");
assert.ok(callbacks.length > 0, "callbacks are set");
callbacks.forEach(cb => cb([]));
assert.equal(survey.getQuestionByName("q1").currentMode, "file", "#1");
@@ -1459,10 +1459,10 @@ QUnit.test("QuestionFile current mode property, camera is available", function (
{ type: "file", name: "q4", sourceType: "file-camera" },
]
});
- assert.equal(survey.getQuestionByName("q1").currentMode, "file");
- assert.equal(survey.getQuestionByName("q2").currentMode, "file");
- assert.equal(survey.getQuestionByName("q3").currentMode, "camera");
- assert.equal(survey.getQuestionByName("q4").currentMode, "file-camera");
+ assert.equal(survey.getQuestionByName("q1").currentMode, "file", "1.1");
+ assert.equal(survey.getQuestionByName("q2").currentMode, "file", "1.2");
+ assert.equal(survey.getQuestionByName("q3").currentMode, "camera", "1.3");
+ assert.equal(survey.getQuestionByName("q4").currentMode, "file-camera", "1.4");
assert.ok(callbacks.length > 0, "callbacks are set");
callbacks.forEach(cb => cb(devices));
diff --git a/packages/survey-core/tests/question_customtests.ts b/packages/survey-core/tests/question_customtests.ts
index bf08c25d64..50f00b6616 100644
--- a/packages/survey-core/tests/question_customtests.ts
+++ b/packages/survey-core/tests/question_customtests.ts
@@ -1902,9 +1902,13 @@ QUnit.test("Check updateElementCss for custom question", function (assert) {
elements: [{ type: "newquestion", name: "q1" }],
});
const question = survey.getAllQuestions()[0];
+ const css1 = question.cssClassesValue;
+ const css2 = question.contentQuestion.cssClassesValue;
+ assert.ok(css1);
+ assert.ok(css2);
question.updateElementCss();
- assert.equal(question.cssClassesValue, undefined);
- assert.equal(question.contentQuestion.cssClassesValue, undefined);
+ assert.notStrictEqual(question.cssClassesValue, css1);
+ assert.notStrictEqual(question.contentQuestion.cssClassesValue, css2);
ComponentCollection.Instance.clear();
});
QUnit.test("onvalueChanging/Changed events", function (assert) {
diff --git a/packages/survey-core/tests/question_paneldynamic_tests.ts b/packages/survey-core/tests/question_paneldynamic_tests.ts
index 166f42bbdf..e3035085fb 100644
--- a/packages/survey-core/tests/question_paneldynamic_tests.ts
+++ b/packages/survey-core/tests/question_paneldynamic_tests.ts
@@ -4851,6 +4851,7 @@ QUnit.test("cssClasses for a question in nested panel dynamic, #2", function (as
const nestedPanel = rootPanel.panels[0].getQuestionByName("panel2");
const panel = nestedPanel.panels[0];
const question = panel.getQuestionByName("q1");
+ assert.equal(question.wasRendered, true, "question.onFirstRendering was called");
assert.ok(question.cssClassesValue.mainRoot, "Main root style is set");
assert.equal(panel.rows.length, 1, "There is one row");
const row = panel.rows[0];
diff --git a/packages/survey-core/tests/question_ratingtests.ts b/packages/survey-core/tests/question_ratingtests.ts
index fd7edca97a..3fc7a9f4fb 100644
--- a/packages/survey-core/tests/question_ratingtests.ts
+++ b/packages/survey-core/tests/question_ratingtests.ts
@@ -343,7 +343,6 @@ QUnit.test("Check cssClasses update when dropdownListModel is set", (assert) =>
const dropdownListModel = new DropdownListModel(q1);
const list: ListModel = dropdownListModel.popupModel.contentComponentData.model as ListModel;
q1.dropdownListModel = dropdownListModel;
- q1.cssClasses;
assert.ok(dropdownListModel.popupModel.cssClass.includes("custom-popup-class"));
assert.equal(list.cssClasses.item, "original-class custom-class");
assert.equal(list.cssClasses.itemSelected, "original-class-selected custom-class-selected");
diff --git a/packages/survey-core/tests/question_tagbox_tests.ts b/packages/survey-core/tests/question_tagbox_tests.ts
index 5b8dd9ead4..430a66da9b 100644
--- a/packages/survey-core/tests/question_tagbox_tests.ts
+++ b/packages/survey-core/tests/question_tagbox_tests.ts
@@ -7,6 +7,7 @@ import { settings } from "../src/settings";
import { QuestionMatrixDynamicModel } from "../src/question_matrixdynamic";
import { ListModel } from "../src/list";
import { PageModel } from "../src/page";
+import { IAction } from "../src/actions/action";
export default QUnit.module("Tagbox question");
@@ -1622,9 +1623,10 @@ QUnit.test("Create tag box in the code, dropdownListModel instance", (assert) =>
const question = new QuestionTagboxModel("q1");
const page = new PageModel("page1");
page.addQuestion(question);
- assert.notOk(question.dropdownListModel, "It is not created yet");
+ assert.notOk(question["dropdownListModelValue"], "It is not created yet");
survey.addPage(page);
- assert.ok(question.dropdownListModel, "It is created");
+ assert.notOk(question["dropdownListModelValue"], "It is not created yet");
+ assert.ok(question.dropdownListModel, "It is created on demand");
});
QUnit.test("Create tag box from json, dropdownListModel instance", (assert) => {
const survey = new SurveyModel({
@@ -1818,4 +1820,43 @@ QUnit.test("The new selected value is always replaced with the the first selecte
}, callbackTimeOutDelta);
done1();
}, onChoicesLazyLoadCallbackTimeOut + callbackTimeOutDelta);
-});
\ No newline at end of file
+});
+QUnit.test("rendering actions id", assert => {
+ const json = {
+ questions: [{
+ type: "tagbox",
+ name: "q1",
+ searchEnabled: false,
+ choices: ["Item1", "Item2"]
+ }]
+ };
+ const survey = new SurveyModel(json);
+ const question = survey.getQuestionByName("q1");
+ assert.notOk(question["dropdownListModelValue"], "It is not created yet");
+ question.id = "el1";
+ const listModel = question.popupModel.contentComponentData.model as ListModel;
+ const actions = listModel.renderedActions;
+ assert.equal(actions.length, 2, "two actions");
+ assert.equal((actions[0]).elementId, "el1i_listItem1", "elementId, action1");
+ assert.equal((actions[1]).elementId, "el1i_listItem2", "elementId, action2");
+ assert.equal((actions[0]).disableTabStop, true, "disableTabStop, action1");
+ assert.equal((actions[1]).disableTabStop, true, "disableTabStop, action2");
+});
+QUnit.test("List actions disableTabStop", assert => {
+ const json = {
+ questions: [{
+ type: "tagbox",
+ name: "q1",
+ searchEnabled: true,
+ choices: ["Item1", "Item2"]
+ }]
+ };
+ const survey = new SurveyModel(json);
+ const question = survey.getQuestionByName("q1");
+ question.dropdownListModel.inputStringRendered = "o";
+ const listModel = question.popupModel.contentComponentData.model as ListModel;
+ const actions = listModel.renderedActions;
+ assert.equal(actions.length, 2, "two actions");
+ assert.equal((actions[0]).disableTabStop, true, "disableTabStop, action1");
+ assert.equal((actions[1]).disableTabStop, true, "disableTabStop, action2");
+});
diff --git a/tests/markup/snapshots/dropdown-input-string.snap.html b/tests/markup/snapshots/dropdown-input-string.snap.html
index 1b2b7dc017..608654e7cd 100644
--- a/tests/markup/snapshots/dropdown-input-string.snap.html
+++ b/tests/markup/snapshots/dropdown-input-string.snap.html
@@ -30,17 +30,17 @@
No data to display
-
+
Ford
-
+
Vauxhall
-
+
Volkswagen
diff --git a/tests/markup/snapshots/matrixdropdown-readonly.snap.html b/tests/markup/snapshots/matrixdropdown-readonly.snap.html
index 103ece95ce..ba26a0c14b 100644
--- a/tests/markup/snapshots/matrixdropdown-readonly.snap.html
+++ b/tests/markup/snapshots/matrixdropdown-readonly.snap.html
@@ -21,12 +21,12 @@
-
+
-
+
@@ -36,12 +36,12 @@
-
+
-
+
diff --git a/tests/markup/snapshots/multipletext-readonly.snap.html b/tests/markup/snapshots/multipletext-readonly.snap.html
index d9b936c3a6..e9fd445dda 100644
--- a/tests/markup/snapshots/multipletext-readonly.snap.html
+++ b/tests/markup/snapshots/multipletext-readonly.snap.html
@@ -7,7 +7,7 @@
Text 1
-
+
@@ -17,7 +17,7 @@
Text 2
-
+
@@ -29,7 +29,7 @@
Text 3
-
+
@@ -39,7 +39,7 @@
Text 4
-
+
diff --git a/tests/markup/snapshots/text-readonly.snap.html b/tests/markup/snapshots/text-readonly.snap.html
index fefc6c25c7..c4ec1b8db3 100644
--- a/tests/markup/snapshots/text-readonly.snap.html
+++ b/tests/markup/snapshots/text-readonly.snap.html
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file