diff --git a/src/actions/action.ts b/src/actions/action.ts index f40c36f837..a32a41ab19 100644 --- a/src/actions/action.ts +++ b/src/actions/action.ts @@ -1,5 +1,5 @@ import { ILocalizableOwner, LocalizableString } from "../localizablestring"; -import { Base } from "../base"; +import { Base, ComputedUpdater } from "../base"; import { surveyLocalization } from "../surveyStrings"; import { property } from "../jsonobject"; import { IListModel, ListModel } from "../list"; @@ -28,7 +28,7 @@ export interface IAction { * @see enabled * @see active */ - visible?: boolean; + visible?: boolean | ComputedUpdater; /** * The action item's title. * @@ -49,7 +49,7 @@ export interface IAction { * @see active * @see visible */ - enabled?: boolean; + enabled?: boolean | ComputedUpdater; enabledIf?: () => boolean; /** * Specifies the visibility of the action item's title. @@ -448,7 +448,7 @@ export class Action extends BaseAction implements IAction, ILocalizableOwner { this._enabled = val; } public getEnabled(): boolean { - if(this.enabledIf) return this.enabledIf(); + if (this.enabledIf) return this.enabledIf(); return this._enabled; } public setComponent(val: string): void { diff --git a/src/base.ts b/src/base.ts index cb08acde96..bdd597e37b 100644 --- a/src/base.ts +++ b/src/base.ts @@ -810,10 +810,10 @@ export class Base { public unRegisterFunctionOnPropertiesValueChanged(names: Array, key: string = null): void { this.unregisterPropertyChangedHandlers(names, key); } - public createCustomLocalizableObj(name: string) { - var locStr = this.getLocalizableString(name); - if (locStr) return; - this.createLocalizableString(name, (this), false, true); + public createCustomLocalizableObj(name: string): LocalizableString { + const locStr = this.getLocalizableString(name); + if(locStr) return locStr; + return this.createLocalizableString(name, (this), false, true); } public getLocale(): string { const locOwner = this.getSurvey(); diff --git a/src/defaultV2-theme/blocks/sd-table.scss b/src/defaultV2-theme/blocks/sd-table.scss index dbdb367bee..b7246b2b8e 100644 --- a/src/defaultV2-theme/blocks/sd-table.scss +++ b/src/defaultV2-theme/blocks/sd-table.scss @@ -284,9 +284,10 @@ fill: $font-questiondescription-color; } - &:hover { + &:hover, + &:focus { background: $primary-light; - + outline: none; svg { fill: $primary; } diff --git a/src/jsonobject.ts b/src/jsonobject.ts index b2a3d96f3a..06c9d91b51 100644 --- a/src/jsonobject.ts +++ b/src/jsonobject.ts @@ -579,7 +579,8 @@ export class CustomPropertiesCollection { prop.serializationProperty && obj.createCustomLocalizableObj ) { - obj.createCustomLocalizableObj(prop.name); + const locStr = obj.createCustomLocalizableObj(prop.name); + locStr.defaultValue = prop.defaultValue; var locDesc = { get: function () { return obj.getLocalizableString(prop.name); @@ -588,7 +589,7 @@ export class CustomPropertiesCollection { Object.defineProperty(obj, prop.serializationProperty, locDesc); var desc = { get: function () { - return obj.getLocalizableStringText(prop.name, prop.defaultValue); + return obj.getLocalizableStringText(prop.name); }, set: function (v: any) { obj.setLocalizableStringText(prop.name, v); diff --git a/src/localizablestring.ts b/src/localizablestring.ts index c9b39ae1d1..f42001d355 100644 --- a/src/localizablestring.ts +++ b/src/localizablestring.ts @@ -54,6 +54,7 @@ export class LocalizableString implements ILocalizableString { public searchText: string; public searchIndex: number; public disableLocalization: boolean; + public defaultValue: string; constructor( public owner: ILocalizableOwner, public useMarkdown: boolean = false, @@ -131,7 +132,7 @@ export class LocalizableString implements ILocalizableString { res = this.onGetLocalizationTextCallback(res); } } - if (!res) res = ""; + if (!res) res = this.defaultValue || ""; return res; } private getRootDialect(loc: string): string { diff --git a/src/survey.ts b/src/survey.ts index 8f0894927e..b5ebd9ee3d 100644 --- a/src/survey.ts +++ b/src/survey.ts @@ -3131,6 +3131,24 @@ export class SurveyModel extends SurveyElementCore this.currentPageChanged(newPage, oldValue); } } + public tryNavigateToPage(page: PageModel): boolean { + if (this.isDesignMode) return false; + const index = this.visiblePages.indexOf(page); + if(index < 0) return false; + if(index === this.currentPageNo) return false; + if (index < this.currentPageNo) { + this.currentPageNo = index; + return true; + } + for (let i = this.currentPageNo; i < index; i++) { + const page = this.visiblePages[i]; + if(!page.validate(true, true)) return false; + page.passed = true; + } + this.currentPage = page; + return true; + } + private updateCurrentPage(): void { if (this.isCurrentPageAvailable) return; this.currentPage = this.firstVisiblePage; diff --git a/src/surveyProgressButtons.ts b/src/surveyProgressButtons.ts index b34d2b6238..d1876936b2 100644 --- a/src/surveyProgressButtons.ts +++ b/src/surveyProgressButtons.ts @@ -29,14 +29,6 @@ export class SurveyProgressButtonsModel { .toString(); } public clickListElement(index: number): void { - if (this.survey.isDesignMode) return; - if (index < this.survey.currentPageNo) { - this.survey.currentPageNo = index; - } - else if (index > this.survey.currentPageNo) { - for (let i: number = this.survey.currentPageNo; i < index; i++) { - if (!this.survey.nextPage()) break; - } - } + this.survey.tryNavigateToPage(this.survey.visiblePages[index]); } } diff --git a/src/surveyToc.ts b/src/surveyToc.ts index 627ccf9216..ae87c58177 100644 --- a/src/surveyToc.ts +++ b/src/surveyToc.ts @@ -7,21 +7,7 @@ import { PopupModel } from "./popup"; import { SurveyModel } from "./survey"; import { IsTouch } from "./utils/devices"; -export function tryNavigateToPage(survey: SurveyModel, page: PageModel) { - if (survey.isDesignMode) return; - const index = survey.visiblePages.indexOf(page); - if (index < survey.currentPageNo) { - survey.currentPageNo = index; - } - else if (index > survey.currentPageNo) { - for (let i = survey.currentPageNo; i < index; i++) { - if (!survey.nextPageUIClick()) return false; - } - } - return true; -} - -export function tryFocusPage(survey: SurveyModel, panel: PanelModelBase) { +export function tryFocusPage(survey: SurveyModel, panel: PanelModelBase): boolean { if (survey.isDesignMode) return true; panel.focusFirstQuestion(); return true; @@ -40,7 +26,7 @@ export function createTOCListModel(survey: SurveyModel, onAction?: () => void) { } !!onAction && onAction(); if (page instanceof PageModel) { - return tryNavigateToPage(survey, page); + return survey.tryNavigateToPage(page); } return tryFocusPage(survey, page); }, diff --git a/tests/jsonobjecttests.ts b/tests/jsonobjecttests.ts index 5f40d90270..e26c8da48d 100644 --- a/tests/jsonobjecttests.ts +++ b/tests/jsonobjecttests.ts @@ -22,6 +22,7 @@ import { SurveyModel } from "../src/survey"; import { CalculatedValue } from "../src/calculatedValue"; import { QuestionHtmlModel } from "../src/question_html"; import { ImageItemValue } from "../src/question_imagepicker"; +import { PageModel } from "../src/page"; class Car extends Base implements ILocalizableOwner { public locale: string; @@ -3111,3 +3112,30 @@ QUnit.test("getRequiredProperties", function (assert) { requiedValues = Serializer.getRequiredProperties("text"); assert.deepEqual(requiedValues, ["name"], "required #3"); }); +QUnit.test("Create localizable property with default value", function (assert) { + Serializer.addProperty("question", { name: "customProp:text", isLocalizable: true, default: "Question text" }); + Serializer.addProperty("page", { name: "customProp:text", isLocalizable: true, default: "Page text" }); + const question = new Question("q1"); + const page = new PageModel("page1"); + assert.equal(question["customProp"], "Question text", "Question prop #1"); + assert.equal(page["customProp"], "Page text", "Page prop #1"); + assert.equal(question.getPropertyValue("customProp"), "Question text", "Question getPropertyValue #1"); + assert.equal(page.getPropertyValue("customProp"), "Page text", "Page getPropertyValue #1"); + + question["customProp"] = "Set question val"; + page["customProp"] = "Set page val"; + assert.equal(question["customProp"], "Set question val", "Question prop #2"); + assert.equal(page["customProp"], "Set page val", "Page prop #2"); + assert.equal(question.getPropertyValue("customProp"), "Set question val", "Question getPropertyValue #2"); + assert.equal(page.getPropertyValue("customProp"), "Set page val", "Page getPropertyValue #2"); + + question.resetPropertyValue("customProp"); + page.resetPropertyValue("customProp"); + assert.equal(question["customProp"], "Question text", "Question prop #3"); + assert.equal(page["customProp"], "Page text", "Page prop #3"); + assert.equal(question.getPropertyValue("customProp"), "Question text", "Question getPropertyValue #3"); + assert.equal(page.getPropertyValue("customProp"), "Page text", "Page getPropertyValue #3"); + + Serializer.removeProperty("question", "customProp"); + Serializer.removeProperty("page", "customProp"); +}); diff --git a/tests/localizablestringtests.ts b/tests/localizablestringtests.ts index 3fa69a0062..4b50f021c9 100644 --- a/tests/localizablestringtests.ts +++ b/tests/localizablestringtests.ts @@ -835,3 +835,24 @@ QUnit.test("Fire onStringChanged when localizationName is set", function (assert locString.localizationName = "completeText"; assert.equal(callCount, 1, "onStringChanged is called"); }); +QUnit.test("Support defaultValue for localizable strings", function (assert) { + const owner = new LocalizableOwnerTester(""); + const locStr1 = new LocalizableString(owner, true); + locStr1.defaultValue = "str1"; + const locStr2 = new LocalizableString(owner, true, "locStr2"); + locStr2.defaultValue = "str2"; + const locStr3 = new LocalizableString(owner, true); + locStr3.localizationName = "completeText"; + locStr3.defaultValue = "str3"; + assert.equal(locStr1.text, "str1", "str1 #1"); + assert.equal(locStr2.text, "str2", "str2 #1"); + assert.equal(locStr3.text, "Complete", "str3 #1"); + + locStr1.text = "new str1"; + assert.equal(locStr1.text, "new str1", "str1 #2"); + + owner.locale = "de"; + assert.equal(locStr1.text, "new str1", "str1 #3"); + assert.equal(locStr2.text, "str2", "str2 #3"); + assert.equal(locStr3.text, "Abschließen", "str3 #3"); +}); diff --git a/tests/surveyProgressButtonsTest.ts b/tests/surveyProgressButtonsTest.ts index fadb1f6db1..a8fc6a745a 100644 --- a/tests/surveyProgressButtonsTest.ts +++ b/tests/surveyProgressButtonsTest.ts @@ -4,7 +4,7 @@ import { SurveyProgressButtonsModel } from "../src/surveyProgressButtons"; export default QUnit.module("SurveyProgressButtons"); QUnit.test("SurveyProgressButtonsModel list elements", function(assert) { - let json: any = { + const json: any = { "pages": [ { "name": "page1", @@ -35,8 +35,8 @@ QUnit.test("SurveyProgressButtonsModel list elements", function(assert) { } ] }; - let survey: SurveyModel = new SurveyModel(json); - let progress: SurveyProgressButtonsModel = new SurveyProgressButtonsModel(survey); + const survey: SurveyModel = new SurveyModel(json); + const progress: SurveyProgressButtonsModel = new SurveyProgressButtonsModel(survey); assert.equal(progress.getListElementCss(0), survey.css.progressButtonsListElementCurrent, "1) Page 1 style is current"); @@ -46,6 +46,7 @@ QUnit.test("SurveyProgressButtonsModel list elements", function(assert) { "", "1) Page 3 style is empty"); progress.clickListElement(2); + assert.equal(survey.currentPageNo, 2, "currentPageNo #1"); assert.equal(progress.getListElementCss(0), survey.css.progressButtonsListElementPassed, "2) Page 1 style is passed"); @@ -57,6 +58,7 @@ QUnit.test("SurveyProgressButtonsModel list elements", function(assert) { "2) Page 3 style is current"); progress.clickListElement(0); + assert.equal(survey.currentPageNo, 0, "currentPageNo #2"); assert.equal(progress.getListElementCss(0), survey.css.progressButtonsListElementPassed + " " + survey.css.progressButtonsListElementCurrent, diff --git a/tests/surveyTOCTests.ts b/tests/surveyTOCTests.ts index 5f67c18a35..d54c5b15b4 100644 --- a/tests/surveyTOCTests.ts +++ b/tests/surveyTOCTests.ts @@ -325,3 +325,68 @@ QUnit.test("TOC shouldn't show search", function (assert) { const tocListModel = createTOCListModel(survey); assert.equal(tocListModel.searchEnabled, false, "Search in TOC should be disabled"); }); +QUnit.test("survey.tryNavigateToPage", function (assert) { + let json: any = { + "pages": [ + { + "name": "page1", + "elements": [ + { + "type": "text", + "name": "question1" + } + ] + }, + { + "name": "page2", + "elements": [ + { + "type": "text", + "name": "question2", + "isRequired": true + } + ] + }, + { + "name": "page3", + "elements": [ + { + "type": "text", + "name": "question3", + "isRequired": true + } + ] + }, + { + "name": "page4", + "elements": [ + { + "type": "text", + "name": "question4" + } + ] + } + ] + }; + const survey = new SurveyModel(json); + const pages = new Array(); + survey.onCurrentPageChanged.add((sender, options) => { + pages.push(options.newCurrentPage.name); + }); + assert.equal(survey.currentPageNo, 0, "currentPageNo #1"); + assert.equal(survey.tryNavigateToPage(survey.pages[3]), false, "navigate #1"); + assert.equal(survey.currentPageNo, 1, "currentPageNo #2"); + assert.equal(survey.tryNavigateToPage(survey.pages[2]), false, "navigate #2"); + assert.equal(survey.currentPageNo, 1, "currentPageNo #3"); + assert.equal(survey.tryNavigateToPage(survey.pages[0]), true, "navigate #3"); + assert.equal(survey.currentPageNo, 0, "currentPageNo #4"); + survey.setValue("question2", "val2"); + assert.equal(survey.tryNavigateToPage(survey.pages[3]), false, "navigate #4"); + assert.equal(survey.currentPageNo, 2, "currentPageNo #4"); + assert.equal(survey.tryNavigateToPage(survey.pages[0]), true, "navigate #5"); + assert.equal(survey.currentPageNo, 0, "currentPageNo #5"); + survey.setValue("question3", "val3"); + assert.equal(survey.tryNavigateToPage(survey.pages[3]), true, "navigate #6"); + assert.equal(survey.currentPageNo, 3, "currentPageNo #6"); + assert.deepEqual(pages, ["page2", "page1", "page3", "page1", "page4"], "Check onCurrentPageChanged"); +}); diff --git a/visualRegressionTests/tests/defaultV2/etalons/question-matrix-detail-focus.png b/visualRegressionTests/tests/defaultV2/etalons/question-matrix-detail-focus.png new file mode 100644 index 0000000000..545719e69b Binary files /dev/null and b/visualRegressionTests/tests/defaultV2/etalons/question-matrix-detail-focus.png differ diff --git a/visualRegressionTests/tests/defaultV2/matrix.ts b/visualRegressionTests/tests/defaultV2/matrix.ts index b140d3e17b..f201478e2e 100644 --- a/visualRegressionTests/tests/defaultV2/matrix.ts +++ b/visualRegressionTests/tests/defaultV2/matrix.ts @@ -656,7 +656,7 @@ frameworks.forEach(framework => { await takeElementScreenshot("question-matrix-dropdown-detail-no-header-expanded.png", questionRoot, t, comparer); await t.click(Selector(".sd-table__cell--detail-button").filterVisible().nth(1)); - await t.hover(questionRoot, { offsetX: 1, offsetY: 1 }); + await t.click(questionRoot, { offsetX: 1, offsetY: 1 }); await takeElementScreenshot("question-matrix-dropdown-detail-no-header.png", questionRoot, t, comparer); }); @@ -736,6 +736,11 @@ frameworks.forEach(framework => { await t.hover(Selector(".sd-table__cell--detail-button")); await takeElementScreenshot("question-matrix-detail-hover.png", questionRoot, t, comparer); + + await t + .click(Selector("body"), { offsetX: 5, offsetY: 5 }); + await t.pressKey("tab"); + await takeElementScreenshot("question-matrix-detail-focus.png", questionRoot, t, comparer); }); });