diff --git a/packages/survey-creator-core/src/entries/index.ts b/packages/survey-creator-core/src/entries/index.ts index 2786bb2b03..48565af4aa 100644 --- a/packages/survey-creator-core/src/entries/index.ts +++ b/packages/survey-creator-core/src/entries/index.ts @@ -89,7 +89,7 @@ export * from "../plugins/undo-redo"; export * from "../plugins/undo-redo/undo-redo-manager"; export * from "../pages-controller"; export * from "../presets/presets"; -export * from "../presets/presets-editor"; +export * from "../presets/editable/presets-editor"; require("../components/property-panel/property-panel-item.scss"); require("../components/property-panel/property-panel.scss"); diff --git a/packages/survey-creator-core/src/presets/editable/presets-editable-base.ts b/packages/survey-creator-core/src/presets/editable/presets-editable-base.ts new file mode 100644 index 0000000000..151acd6450 --- /dev/null +++ b/packages/survey-creator-core/src/presets/editable/presets-editable-base.ts @@ -0,0 +1,110 @@ +import { Helpers, SurveyModel } from "survey-core"; +import { SurveyCreatorModel } from "../../creator-base"; +import { CreatorPresetBase } from "../presets-base"; + +export class CreatorPresetEditableBase { + public parent: CreatorPresetEditableBase; + public children: Array = []; + public constructor(public preset: CreatorPresetBase) { + } + public get path() { return this.preset.getPath(); } + protected getJsonPath(model: SurveyModel): string { return this.path; } + public get fullPath() { + let prefix = this.parent ? this.parent.fullPath : ""; + if(this.path && prefix) { + prefix += "_"; + } + return prefix + this.path; + } + public get pageName(): string { return "page_" + this.fullPath; } + public createPages(): Array { + const res = []; + const mainPage = this.createMainPage(); + if(mainPage) { + res.push(mainPage); + } + this.children.forEach(item => { + const pages = item.createPages(); + if(Array.isArray(pages)) { + pages.forEach(page => res.push(page)); + } + }); + return res; + } + public validate(model: SurveyModel): boolean { + if(!this.validateCore(model)) return false; + for(let i = 0; i < this.children.length; i ++) { + if(!this.children[i].validate(model)) return false; + } + return true; + } + protected validateCore(model: SurveyModel): boolean { + return true; + } + protected createMainPage(): any { + const res = this.createMainPageCore(); + if(res) { + res.name = this.pageName; + } + return res; + } + protected getBoolVisibleIf(name: string): string { return "{" + name + "}=true"; } + protected getTextVisibleIf(name: string, val: string): string { return "{" + name + "}='" + val +"'"; } + protected getNotEmptyVisibleIf(name: string): string { return "{" + name + "} notempty"; } + protected createMainPageCore(): any { return undefined; } + public getJsonValue(model: SurveyModel): any { + const page = model.getPageByName(this.pageName); + const core = page && page.isVisible ? this.getJsonValueCore(model) : undefined; + let hasValue = !!core; + const res = hasValue ? core : {}; + this.children.forEach(item => { + const val = item.getJsonValue(model); + if(!!val) { + hasValue = true; + res[item.getJsonPath(model)] = val; + } + }); + return hasValue ? res : undefined; + } + public setupQuestions(model: SurveyModel, creator: SurveyCreatorModel): void { + this.setupQuestionsCore(model, creator); + this.children.forEach(item => { + item.setupQuestions(model, creator); + }); + } + public setupOnCurrentPage(model: SurveyModel, creator: SurveyCreatorModel): void { + if(model.currentPage.name === this.pageName) { + this.setupOnCurrentPageCore(model, creator); + } + this.children.forEach(item => { + item.setupOnCurrentPage(model, creator); + }); + } + public updateOnValueChanged(model: SurveyModel, creator: SurveyCreatorModel, name: string): void { + this.updateOnValueChangedCore(model, creator, name); + this.children.forEach(item => { + item.updateOnValueChanged(model, creator, name); + }); + } + public updateOnMatrixDetailPanelVisibleChanged(model: SurveyModel, creator: SurveyCreatorModel, options: any): void { + this.updateOnMatrixDetailPanelVisibleChangedCore(model, creator, options); + this.children.forEach(item => { + item.updateOnMatrixDetailPanelVisibleChanged(model, creator, options); + }); + } + public setupQuestionsValue(model: SurveyModel, json: any, creator: SurveyCreatorModel): void { + this.setupQuestionsValueCore(model, json, creator); + this.children.forEach(item => { + item.setupQuestionsValue(model, !!json ? json[item.path]: undefined, creator); + }); + } + protected setupQuestionsCore(model: SurveyModel, creator: SurveyCreatorModel): void { } + protected setupQuestionsValueCore(model: SurveyModel, json: any, creator: SurveyCreatorModel): void {} + protected getJsonValueCore(model: SurveyModel): any { return undefined; } + protected setupOnCurrentPageCore(model: SurveyModel, creator: SurveyCreatorModel): void {} + protected updateOnValueChangedCore(model: SurveyModel, creator: SurveyCreatorModel, name: string): void {} + protected updateOnMatrixDetailPanelVisibleChangedCore(model: SurveyModel, creator: SurveyCreatorModel, options: any): void {} + protected copyJson(json: any): any { + return Helpers.getUnbindValue(json); + } +} \ No newline at end of file diff --git a/packages/survey-creator-core/src/presets/editable/presets-editable-properties.ts b/packages/survey-creator-core/src/presets/editable/presets-editable-properties.ts new file mode 100644 index 0000000000..50dc63088d --- /dev/null +++ b/packages/survey-creator-core/src/presets/editable/presets-editable-properties.ts @@ -0,0 +1,390 @@ +import { JsonObjectProperty, ItemValue, MatrixDropdownRowModelBase, QuestionDropdownModel, + QuestionMatrixDynamicModel, Base, Serializer, SurveyModel, ElementContentVisibilityChangedEvent, + matrixDropdownColumnTypes } from "survey-core"; +import { CreatorPresetEditableBase } from "./presets-editable-base"; +import { SurveyCreatorModel } from "../../creator-base"; +import { defaultPropertyGridDefinition, ISurveyPropertyGridDefinition, ISurveyPropertiesDefinition } from "../../question-editor/definition"; +import { SurveyQuestionProperties } from "../../question-editor/properties"; +import { editorLocalization } from "../../editorLocalization"; +import { PropertyGridModel } from "../../../src/property-grid"; +import { QuestionEmbeddedSurveyModel } from "../../components/embedded-survey"; + +export class SurveyQuestionPresetProperties extends SurveyQuestionProperties { + constructor(obj, className: string, propertyGridDefinition: ISurveyPropertyGridDefinition) { + super(obj, null, className, null, null, null, propertyGridDefinition); + } + protected getIsPropertyVisible(prop: JsonObjectProperty): boolean { + return prop.visible !== false; + } +} + +const presetPropertiesBaseClasses = ["question", "matrixdropdownbase", "selectbase", "panelbase", "matrixdropdowncolumn@default", "matrixdropdowncolumn@selectbase"]; + +export class SurveyQuestionPresetPropertiesDetail { + private propertiesHash = {}; + public classes = new Array(); + private properties: SurveyQuestionPresetProperties; + private propertyGridValue: PropertyGridModel; + private allPropertiesNames: Array; + constructor(private className: string, private currentJson: ISurveyPropertyGridDefinition) { + const cls = {}; + const obj = this.createObj(); + this.properties = new SurveyQuestionPresetProperties(obj, className, currentJson); + this.allPropertiesNames = this.properties.getAllVisiblePropertiesNames(true); + const objProps = {}; + Serializer.getPropertiesByObj(obj).forEach(prop => objProps[prop.name] = prop); + this.allPropertiesNames.forEach(name => { + const prop = objProps[name]; + if(prop) { + const propClassName = this.getPropClassName(prop); + this.propertiesHash[name] = propClassName; + cls[propClassName] = true; + } + }); + for(let i = 0; i < presetPropertiesBaseClasses.length; i ++) { + const cl = presetPropertiesBaseClasses[i]; + if(cls[cl]) { + this.classes.push(cl); + } + } + if(this.classes.indexOf(className) < 0) { + this.classes.push(className); + } + this.propertyGridValue = new PropertyGridModel(obj, undefined, this.currentJson); + } + private createObj(): Base { + if(this.className === "survey") return new SurveyModel(); + const ind = this.className.indexOf("@"); + if(ind < 0) return Serializer.createClass(this.className); + const clName = this.className.substring(0, ind); + const postFix = this.className.substring(ind + 1); + const res = Serializer.createClass(clName); + if(res.cellType) { + res.cellType = postFix; + } + return res; + } + public dispose(): void { + //TODO + } + public get propertyGrid(): PropertyGridModel { return this.propertyGridValue; } + public getRows(): Array { + const rows = []; + this.properties.getTabs().forEach(tab => { + const row: any = { name: tab.name, items: [] }; + tab.properties.forEach(prop => { + row.items.push(prop.name); + }); + rows.push(row); + }); + return rows; + } + public getRankingChoices(matrix: QuestionMatrixDynamicModel, row: MatrixDropdownRowModelBase): Array { + const val = matrix.value; + const usedItems = {}; + if(Array.isArray(val)) { + const rowIndex = matrix.visibleRows.indexOf(row); + for(let i = 0; i < val.length; i ++) { + const items = val[i].items; + if(i !== rowIndex && Array.isArray(items)) { + items.forEach(v => usedItems[v] = true); + } + } + } + const res = []; + this.allPropertiesNames.forEach(name => { + if(!usedItems[name]) { + res.push(new ItemValue(name, editorLocalization.getPropertyNameInEditor(this.className, name))); + } + }); + return res; + } + public updatePropertyGrid(val: Array): void { + const definition: ISurveyPropertyGridDefinition = { autoGenerateProperties: false, classes: {} }; + this.updateCurrentJsonCore(definition.classes, val); + this.propertyGrid.setPropertyGridDefinition(definition); + } + public updateCurrentJson(val: Array): void { + this.updateCurrentJsonCore(this.currentJson.classes, val); + } + private updateCurrentJsonCore(curJsonClasses: ISurveyPropertiesDefinition, val: Array): void { + if(!Array.isArray(val) || val.length === 0) return; + const tabNames = []; + this.classes.forEach(cl => { + this.updateCurrentJsonClass(curJsonClasses, val, cl, tabNames); + }); + } + private updateCurrentJsonClass(curJsonClasses: ISurveyPropertiesDefinition, val: Array, clName: string, tabNames: Array): void { + const properties = []; + const tabs = []; + const tabStep = 100; + + val.forEach(tab => { + const clVal = tab.items; + if(Array.isArray(clVal)) { + const classesIndeces = []; + this.classes.forEach(cl => classesIndeces.push(0)); + const propertiesIndeces = {}; + for(let i = 0; i < clVal.length; i ++) { + const clName = this.propertiesHash[clVal[i]]; + let clIndex = this.classes.indexOf(clName); + if(clIndex < 0) continue; + const nextStep = 10000 / Math.pow(10, clIndex); + let max = 0; + for(let j = 0; j <= clIndex; j ++) { + if(classesIndeces[j] > max) max = classesIndeces[j]; + } + const visIndex = max + nextStep; + propertiesIndeces[clVal[i]] = visIndex; + classesIndeces[clIndex] = visIndex; + } + clVal.forEach(propName => { + if(this.propertiesHash[propName] === clName) { + const tabName = tab.name !== "general" ? tab.name : undefined; + if(!!tabName && tabNames.indexOf(tab.name) < 0) { + tabNames.push(tab.name); + tabs.push({ name: tab.name, index: tabNames.length * tabStep }); + } + const item: any = { name: propName, index: propertiesIndeces[propName] }; + if(!!tabName) { + item.tab = tabName; + } + properties.push(item); + } + }); + } + }); + curJsonClasses[clName] = { properties: properties, tabs: tabs }; + } + private getPropClassName(prop: JsonObjectProperty): string { + const clName = prop.classInfo.name; + for(let i = 1; i < presetPropertiesBaseClasses.length; i ++) { + const cl = presetPropertiesBaseClasses[i]; + if(clName === cl || Serializer.isDescendantOf(clName, cl)) return this.getClassName(cl); + } + if(clName === this.className) return this.className; + return this.getClassName("question"); + } + private getClassName(className: string): string { + const ind = this.className.indexOf("@"); + if(ind < 0) return className; + const clName = this.className.substring(0, ind); + if(clName === className || className === "question") { + className = "default"; + } + return clName + "@" + className; + } +} + +export class CreatorPresetEditablePropertyGridDefinition extends CreatorPresetEditableBase { + private currentJson: ISurveyPropertyGridDefinition; + private currentProperties: SurveyQuestionPresetPropertiesDetail; + private currentClassName: string; + public createMainPageCore(): any { + const parent = (this.parent); + return { + title: "Property Grid categories", + elements: [ + { + type: "boolean", + name: this.nameShow, + title: "Do you want to configure Property Grid categories and properties?" + }, + { + type: "dropdown", + name: this.nameSelector, + visibleIf: this.getBoolVisibleIf(this.nameShow), + clearIfInvisible: "onHidden", + title: "Select element to setup a property grid for it", + }, + { + type: "panel", + name: "propPanel", + visibleIf: this.getNotEmptyVisibleIf(this.nameSelector), + elements: [ + { + type: "matrixdynamic", + name: this.nameMatrix, + allowRowsDragAndDrop: true, + showHeader: false, + titleLocation: "hidden", + addRowText: "Add New Category", + columns: [ + { cellType: "text", name: "name", title: "Category name", isUnique: true, isRequired: true, enableIf: "{row.name} <> 'general'" } + ], + detailPanelMode: "underRowSingle", + detailElements: [ + { + type: "ranking", + name: "items", + selectToRankEnabled: true, + selectToRankAreasLayout: "horizontal", + titleLocation: "hidden", + selectToRankEmptyRankedAreaText: "Drag properties to hide them", + selectToRankEmptyUnrankedAreaText: "Drag properties here" + } + ] + }, + { + type: "embeddedsurvey", + name: this.namePropertyGrid, + startWithNewLine: false + } + ] + } + ] + }; + } + public getJsonValueCore(model: SurveyModel): any { + if(model.getValue(this.nameShow) !== true) return undefined; + this.updateCurrentJson(model); + return this.currentJson; + } + protected setupQuestionsCore(model: SurveyModel, creator: SurveyCreatorModel): void { + this.getSelector(model).choices = this.getSelectorChoices(creator); + this.getMatrix(model).lockedRowCount = 1; + } + protected updateOnMatrixDetailPanelVisibleChangedCore(model: SurveyModel, creator: SurveyCreatorModel, options: any): void { + if(options.question.name === this.nameMatrix) { + this.onDetailPanelShowingChanged(model, options.row); + this.expandEmbeddedSurveyPanel(model); + } + } + private isMatrixValueChanged: boolean; + private isMatrixValueSetting: boolean; + protected updateOnValueChangedCore(model: SurveyModel, creator: SurveyCreatorModel, name: string): void { + if(name === this.nameMatrix && !this.isMatrixValueSetting) { + this.isMatrixValueChanged = true; + if(this.currentProperties) { + this.currentProperties.updatePropertyGrid(model.getValue(name)); + this.updateEmbeddedSurvey(model); + } + } + if(name !== this.nameSelector) return; + this.isMatrixValueSetting = true; + this.updateCurrentJson(model); + if(this.currentProperties) { + this.currentProperties.dispose(); + this.currentProperties = null; + } + const selQuestion = this.getSelector(model); + this.currentClassName = selQuestion.value; + if(!this.currentClassName) return; + const matrix = this.getMatrix(model); + this.currentProperties = new SurveyQuestionPresetPropertiesDetail(this.currentClassName, this.currentJson); + matrix.rowCount = 0; + matrix.value = this.currentProperties.getRows(); + this.updateEmbeddedSurvey(model); + this.isMatrixValueChanged = false; + this.isMatrixValueSetting = false; + } + private updateEmbeddedSurvey(model: SurveyModel): void { + const propGridQuestion = this.getPropertyGridQuestion(model); + const survey = this.currentProperties.propertyGrid.survey; + propGridQuestion.embeddedSurvey = survey; + this.expandEmbeddedSurveyPanel(model); + survey.onElementContentVisibilityChanged.add((sender, options) => { + this.onElementContentVisibilityChanged(model, options); + }); + } + private isContentVisibilityChanging: boolean; + private expandEmbeddedSurveyPanel(model: SurveyModel): void { + if(this.isContentVisibilityChanging) return; + const propGridSurvey = this.currentProperties.propertyGrid.survey; + if(!propGridSurvey) return; + const matrix = this.getMatrix(model); + let name = ""; + matrix.visibleRows.forEach(row => { + if(row.isDetailPanelShowing) { + name = row.getValue("name"); + } + }); + const panel = !!name ? propGridSurvey.getPanelByName(name) : undefined; + this.isContentVisibilityChanging = true; + if(panel) { + panel.expand(); + } else { + propGridSurvey.getAllPanels(true).forEach(panel => panel.collapse()); + } + this.isContentVisibilityChanging = false; + } + private onElementContentVisibilityChanged(model: SurveyModel, options: ElementContentVisibilityChangedEvent): void { + if(this.isContentVisibilityChanging) return; + this.isContentVisibilityChanging = true; + const matrix = this.getMatrix(model); + if(matrix) { + const isExpand = options.element.isExpanded; + const name = options.element.name; + matrix.visibleRows.forEach(row => { + if(isExpand && row.getValue("name") === name) { + row.showDetailPanel(); + } else { + row.hideDetailPanel(); + } + }); + } + this.isContentVisibilityChanging = false; + } + protected setupQuestionsValueCore(model: SurveyModel, json: any, creator: SurveyCreatorModel): void { + model.setValue(this.nameShow, !!json); + if(!json) { + json = this.copyJson(defaultPropertyGridDefinition); + } + this.currentJson = json; + this.currentJson.autoGenerateProperties = false; + } + private get nameShow() { return this.fullPath + "_show"; } + private getMatrix(model: SurveyModel): QuestionMatrixDynamicModel { return model.getQuestionByName(this.nameMatrix); } + private getSelector(model: SurveyModel): QuestionDropdownModel { return model.getQuestionByName(this.nameSelector); } + private getPropertyGridQuestion(model: SurveyModel): QuestionEmbeddedSurveyModel { return model.getQuestionByName(this.namePropertyGrid); } + private get nameMatrix() { return this.fullPath + "_matrix"; } + private get nameSelector() { return this.fullPath + "_selector"; } + private get namePropertyGrid() { return this.fullPath + "_propgrid"; } + private onDetailPanelShowingChanged(model: SurveyModel, row: MatrixDropdownRowModelBase): void { + if(!row.isDetailPanelShowing || !this.currentProperties) return; + const classes = this.currentProperties.classes; + const matrix = this.getMatrix(model); + const q = row.detailPanel.getQuestionByName("items"); + q.choices = this.currentProperties.getRankingChoices(matrix, row); + } + private getSelectorChoices(creator: SurveyCreatorModel): Array { + const classes = ["survey", "page", "panel"]; + const toolboxItems = {}; + creator.toolbox.getDefaultItems([], false, true, true).forEach(item => { + toolboxItems[item.id] = true; + }); + + Serializer.getChildrenClasses("question", true).forEach(cl => { + if(toolboxItems[cl.name]) { + classes.push(cl.name); + } + }); + const res = []; + classes.forEach(str => res.push(new ItemValue(str, this.getSelectorItemTitle(str)))); + const columnPrefix = "matrixdropdowncolumn@"; + res.push(new ItemValue(columnPrefix + "default", this.getColumnItemTitle(""))); + for(let key in matrixDropdownColumnTypes) { + res.push(new ItemValue(columnPrefix + key, this.getColumnItemTitle(key))); + } + return res; + } + private getSelectorItemTitle(name: string): string { + if(name === "survey") return editorLocalization.getString("ed.surveyTypeName"); + if(name === "page") return editorLocalization.getString("ed.pageTypeName"); + return editorLocalization.getString("qt." + name); + } + private getColumnItemTitle(name: string): string { + const columnTitle = editorLocalization.getString("ed.columnTypeName"); + const postFix = !name ? "default" : this.getSelectorItemTitle(name); + return columnTitle + ": " + postFix; + } + private updateCurrentJson(model: SurveyModel): void { + if(!this.isMatrixValueChanged) return; + this.isMatrixValueChanged = false; + if(this.currentProperties) { + this.currentProperties.updateCurrentJson(model.getValue(this.nameMatrix)); + } + } +} +export class CreatorEditablePresetPropertyGrid extends CreatorPresetEditableBase { +} diff --git a/packages/survey-creator-core/src/presets/editable/presets-editable-tabs.ts b/packages/survey-creator-core/src/presets/editable/presets-editable-tabs.ts new file mode 100644 index 0000000000..2db2026773 --- /dev/null +++ b/packages/survey-creator-core/src/presets/editable/presets-editable-tabs.ts @@ -0,0 +1,74 @@ +import { ItemValue, Question, SurveyModel } from "survey-core"; +import { CreatorPresetEditableBase } from "./presets-editable-base"; +import { SurveyCreatorModel } from "../../creator-base"; +import { editorLocalization } from "../../editorLocalization"; + +export class CreatorPresetEditableTabs extends CreatorPresetEditableBase { + public createMainPageCore(): any { + const nameShow = this.nameShow; + const visibleIf = this.getBoolVisibleIf(nameShow); + return { + title: "Tabs customization", + elements: [ + { + type: "boolean", + name: nameShow, + title: "Do you want to setup Creator tabs?", + }, + { + type: "ranking", + name: this.nameItems, + title: "Please order the Creator tabs", + selectToRankEnabled: true, + minSelectedChoices: 1, + selectToRankAreasLayout: "vertical", + visibleIf: visibleIf + }, + { + type: "dropdown", + name: this.nameActiveTab, + title: "Select the default active tab (the first tab is active if this field is empty)", + choicesFromQuestion: this.nameItems, + choicesFromQuestionMode: "selected", + visibleIf: visibleIf + } + ] + }; + } + protected getJsonValueCore(model: SurveyModel): any { + if(!model.getValue(this.nameShow)) return undefined; + const items = model.getValue(this.nameItems); + if(!Array.isArray(items)) return undefined; + const val: any = { items: items }; + const activeTab = model.getValue(this.nameActiveTab); + if(activeTab) { + val.activeTab = activeTab; + } + return val; + } + protected setupQuestionsCore(model: SurveyModel, creator: SurveyCreatorModel): void { + const q = model.getQuestionByName(this.nameItems); + if (q) { + const choices = []; + creator.getAvailableTabNames().forEach(tab => choices.push(new ItemValue(tab, this.getTabTitle(tab)))); + + q.choices = choices; + } + } + protected setupQuestionsValueCore(model: SurveyModel, json: any, creator: SurveyCreatorModel): void { + json = json || {}; + const items = json["items"] || []; + model.setValue(this.nameShow, items.length > 0); + model.setValue(this.nameItems, items.length > 0 ? items : creator.getTabNames()); + model.setValue(this.nameActiveTab, json["activeTab"] || creator.activeTab); + } + private getTabTitle(name: string): string { + if(name === "preview") name = "testSurvey"; + if(name === "editor") name = "jsonEditor"; + if(name === "theme") name = "themeSurvey"; + return editorLocalization.getString("ed." + name); + } + private get nameShow() { return this.path + "_show"; } + private get nameItems() { return this.path + "_items"; } + private get nameActiveTab() { return this.path + "_activeTab"; } +} \ No newline at end of file diff --git a/packages/survey-creator-core/src/presets/editable/presets-editable-toolbox.ts b/packages/survey-creator-core/src/presets/editable/presets-editable-toolbox.ts new file mode 100644 index 0000000000..00929fe368 --- /dev/null +++ b/packages/survey-creator-core/src/presets/editable/presets-editable-toolbox.ts @@ -0,0 +1,291 @@ +import { ItemValue, MatrixDropdownRowModelBase, QuestionMatrixDynamicModel, QuestionRankingModel, Serializer, SurveyModel } from "survey-core"; +import { CreatorPresetEditableBase } from "./presets-editable-base"; +import { SurveyCreatorModel } from "../../creator-base"; +import { SurveyJSON5 } from "../../json5"; + +export class CreatorPresetEditableToolboxDefinition extends CreatorPresetEditableBase { + public createMainPageCore(): any { + return { + title: "Toolbox items definition", + elements: [ + { + type: "boolean", + name: this.nameShow, + title: "Update existing toolbox items and create new items" + }, + { + type: "matrixdynamic", + name: this.nameMatrix, + visibleIf: this.getBoolVisibleIf(this.nameShow), + title: "Define items definition", + rowCount: 0, + addRowText: "Add New Item Defintion", + columns: [ + { cellType: "text", name: "name", title: "Name", isUnique: true, isRequired: true }, + { cellType: "text", name: "iconName", title: "Icon Name" }, + { cellType: "text", name: "title", title: "Title" } + ], + detailPanelMode: "underRow", + detailElements: [ + { type: "text", name: "tooltip", title: "Tooltip" }, + { type: "comment", name: "json", title: "JSON that will be used on clicking item", rows: 15 } + ] + } + ] + }; + } + protected setupQuestionsCore(model: SurveyModel, creator: SurveyCreatorModel): void { + const matrix = this.getMatrix(model); + const nameColumn = matrix.getColumnByName("name"); + const iconNameColumn = matrix.getColumnByName("iconName"); + const names = []; + const iconNames = []; + creator.toolbox.getDefaultItems([], false, true, true).forEach(item => { + names.push(item.id); + iconNames.push(item.iconName || ("icon-" + item.id)); + }); + names.sort(); + iconNames.sort(); + nameColumn["dataList"] = names; + iconNameColumn["dataList"] = iconNames; + } + protected validateCore(model: SurveyModel): boolean { + const matrix = this.getMatrix(model); + const val = matrix.value; + if(!Array.isArray(val)) return true; + for(let rowIndex = 0; rowIndex < val.length; rowIndex ++) { + const json = val[rowIndex]["json"]; + if(!!json) { + if(!this.validateJson(json)) { + const row = matrix.visibleRows[rowIndex]; + row.showDetailPanel(); + const jsonQuestion = row.getQuestionByName("json"); + jsonQuestion.addError("The json is invalid"); // + jsonQuestion.focus(); + return false; + } + } + } + return true; + } + public getJsonValueCore(model: SurveyModel): any { + const matrix = this.getMatrix(model); + const value = matrix.value; + if(!Array.isArray(value) || value.length === 0) return undefined; + const res = []; + for(let i = 0; i < value.length; i ++) { + const val = {}; + const item = value[i]; + for(let key in item) { + const itemVal = key === "json" ? this.parseJson(item[key]) : item[key]; + if(!!itemVal) { + val[key] = itemVal; + } + } + res.push(val); + } + return res; + } + protected setupQuestionsValueCore(model: SurveyModel, json: any, creator: SurveyCreatorModel): void { + model.setValue(this.nameShow, !!json); + json = json || []; + const question = this.getMatrix(model); + const value = []; + json.forEach(item => { + const val = {}; + for(let key in item) { + val[key] = key === "json" ? JSON.stringify(item[key], null, 2) : item[key]; + } + value.push(val); + }); + question.value = value; + } + private getMatrix(model: SurveyModel): QuestionMatrixDynamicModel { + return model.getQuestionByName(this.nameMatrix); + } + private get nameMatrix() { return this.fullPath + "_matrix"; } + public get nameShow() { return this.fullPath + "_show"; } + private validateJson(text: string): boolean { + text = text.trim(); + if(!text) return true; + const json = this.parseJson(text); + if(!json || !json.type) return false; + const obj = Serializer.createClass(json.type, json); + return !!obj; + } + private parseJson(text: string): any { + try { + const res = new SurveyJSON5().parse(text); + return res; + } catch(e) { + return undefined; + } + } +} + +export class CreatorPresetEditableToolboxConfigurator extends CreatorPresetEditableBase { + private defaultItems: ItemValue[]; + public createMainPageCore(): any { + return { + title: "Setup toolbox", + elements: [ + { + type: "boolean", + name: this.nameCategoriesShow, + title: "Setup toolbox items and categories" + }, + { + type: "buttongroup", + name: this.nameCategoriesMode, + title: "Do you want to have categories or plain items", + defaultValue: "categories", + choices: [{ value: "categories", text: "Categories" }, { value: "items", text: "No categories" }], + visibleIf: this.getBoolVisibleIf(this.nameCategoriesShow), + clearIfInvisible: "onHidden", + }, + { + type: "matrixdynamic", + name: this.nameCategories, + visibleIf: this.getTextVisibleIf(this.nameCategoriesMode, "categories"), + minRowCount: 1, + allowRowsDragAndDrop: true, + showHeader: false, + columns: [ + { cellType: "text", name: "name", title: "Category Name", isUnique: true, isRequired: true }, + { cellType: "expression", name: "count", title: "Number of items in category", expression: "{row.items.length}" } + ], + detailPanelMode: "underRowSingle", + detailElements: [ + { + type: "ranking", + name: "items", + titleLocation: "hidden", + selectToRankEnabled: true, + minSelectedChoices: 1, + selectToRankAreasLayout: "horizontal" + } + ] + }, + { + type: "ranking", + name: this.nameItems, + visibleIf: this.getTextVisibleIf(this.nameCategoriesMode, "items"), + titleLocation: "hidden", + selectToRankEnabled: true, + minSelectedChoices: 1, + selectToRankAreasLayout: "horizontal", + } + ] + }; + } + public get nameCategoriesShow() { return this.fullPath + "_show"; } + public get nameCategoriesMode() { return this.fullPath + "_mode"; } + private get nameItems() { return this.fullPath + "_items"; } + private get nameCategories() { return this.fullPath + "_categories"; } + protected getJsonPath(model: SurveyModel): string { + return model.getValue(this.nameCategoriesMode); + } + public getJsonValueCore(model: SurveyModel): any { + const mode = model.getValue(this.nameCategoriesMode); + if(mode === "items") return model.getValue(this.nameItems); + if(mode === "categories") return this.getCategoriesJson(model); + } + private getCategoriesJson(model: SurveyModel): any { + const res = model.getValue(this.nameCategories); + if(!Array.isArray(res)) return undefined; + res.forEach(item => { + delete item["count"]; + }); + return res; + } + protected updateOnMatrixDetailPanelVisibleChangedCore(model: SurveyModel, creator: SurveyCreatorModel, options: any): void { + if(options.question.name === this.nameCategories) { + this.onDetailPanelShowingChanged(options.row); + } + } + protected setupOnCurrentPageCore(model: SurveyModel, creator: SurveyCreatorModel): void { + this.defaultItems = this.getDefaultToolboxItems(model, creator); + this.getQuestionItems(model).choices = this.defaultItems; + } + protected setupQuestionsCore(model: SurveyModel, creator: SurveyCreatorModel): void { + this.defaultItems = this.getDefaultToolboxItems(model, creator); + this.getQuestionItems(model).choices = this.defaultItems; + } + protected setupQuestionsValueCore(model: SurveyModel, json: any, creator: SurveyCreatorModel): void { + const val = []; + creator.toolbox.items.forEach(item => val.push(item.id)); + this.getQuestionItems(model).value = val; + const nameCategories = {}; + const categories = []; + creator.toolbox.items.forEach(item => { + const category = item.category; + if(!!category) { + if(!nameCategories[category]) { + const row = { name: category, items: [item.name] }; + nameCategories[category] = row; + categories.push(row); + } else { + nameCategories[category].items.push(item.name); + } + } + }); + model.setValue(this.nameCategories, categories); + this.getQuestionCategories(model).visibleRows.forEach(row => { + row.onDetailPanelShowingChanged = () => { + this.onDetailPanelShowingChanged(row); + }; + }); + } + private getQuestionItems(model: SurveyModel): QuestionRankingModel { + return model.getQuestionByName(this.nameItems); + } + private getQuestionCategories(model: SurveyModel): QuestionMatrixDynamicModel { return model.getQuestionByName(this.nameCategories); } + private onDetailPanelShowingChanged(row: MatrixDropdownRowModelBase): void { + if(!row.isDetailPanelShowing) return; + row.getQuestionByName("items").choices = this.getRankingChoices(row); + } + private getDefaultToolboxItems(model: SurveyModel, creator: SurveyCreatorModel): ItemValue[] { + const items = {}; + creator.toolbox.getDefaultItems([], false, true, true).forEach(item => { + items[item.id] = item.title; + }); + const definitionVal = model.getValue("toolbox_definition_matrix"); + if(Array.isArray(definitionVal)) { + definitionVal.forEach(item => { + const key = item.name; + if(!!key && !items[key] || !!item.title) { + items[key] = item.title || key; + } + }); + } + const res = []; + for(let key in items) { + res.push(new ItemValue(key, items[key])); + } + + return res; + } + private getRankingChoices(row: MatrixDropdownRowModelBase): Array { + const res = []; + const model = row.getSurvey(); + const matrix = this.getQuestionCategories(model); + if(!Array.isArray(this.defaultItems)) return res; + const val = model.getValue(this.nameCategories); + const usedItems = {}; + if(Array.isArray(val)) { + const rowIndex = matrix.visibleRows.indexOf(row); + for(let i = 0; i < val.length; i ++) { + if(i !== rowIndex && Array.isArray(val[i].items)) { + val[i].items.forEach(v => usedItems[v] = true); + } + } + } + this.defaultItems.forEach(item => { + if(!usedItems[item.id]) { + res.push(new ItemValue(item.id, item.title)); + } + }); + return res; + } +} +export class CreatorPresetEditableToolbox extends CreatorPresetEditableBase {} \ No newline at end of file diff --git a/packages/survey-creator-core/src/presets/presets-editor.ts b/packages/survey-creator-core/src/presets/editable/presets-editor.ts similarity index 66% rename from packages/survey-creator-core/src/presets/presets-editor.ts rename to packages/survey-creator-core/src/presets/editable/presets-editor.ts index 09f5029f4f..cd64bc396b 100644 --- a/packages/survey-creator-core/src/presets/presets-editor.ts +++ b/packages/survey-creator-core/src/presets/editable/presets-editor.ts @@ -1,7 +1,10 @@ -import { SurveyCreatorModel } from "../creator-base"; -import { CreatorPreset, ICreatorPresetData } from "./presets"; +import { SurveyCreatorModel } from "../../creator-base"; +import { CreatorPreset, ICreatorPresetData } from "../presets"; import { SurveyModel } from "survey-core"; -import { CreatorPresetEditableBase } from "./presets-base"; +import { CreatorPresetEditableBase } from "./presets-editable-base"; +import { CreatorPresetEditableToolbox, CreatorPresetEditableToolboxConfigurator, CreatorPresetEditableToolboxDefinition } from "./presets-editable-toolbox"; +import { CreatorPresetEditableTabs } from "./presets-editable-tabs"; +import { CreatorEditablePresetPropertyGrid, CreatorPresetEditablePropertyGridDefinition } from "./presets-editable-properties"; export class CreatorPresetEditor { private presetValue: CreatorPreset; @@ -67,10 +70,32 @@ export class CreatorPresetEditor { } return true; } + private createEditableCore(preset: CreatorPreset, fullPath: string): CreatorPresetEditableBase { + if(fullPath === "tabs") return new CreatorPresetEditableTabs(preset); + if(fullPath === "toolbox") return new CreatorPresetEditableToolbox(preset); + if(fullPath === "toolbox_definition") return new CreatorPresetEditableToolboxDefinition(preset); + if(fullPath === "toolbox_") return new CreatorPresetEditableToolboxConfigurator(preset); + if(fullPath === "propertyGrid") return new CreatorEditablePresetPropertyGrid(preset); + if(fullPath === "propertyGrid_definition") return new CreatorPresetEditablePropertyGridDefinition(preset); + return undefined; + } + private createEditable(preset: CreatorPreset, parent: CreatorPresetEditableBase, fullPath: string): CreatorPresetEditableBase { + const editable = this.createEditableCore(preset, fullPath); + if(editable) { + preset.children.forEach(item => { + const child = this.createEditable(item, editable, fullPath + "_" + item.getPath()); + if(child) { + editable.children.push(child); + child.parent = editable; + } + }); + } + return editable; + } private createEditablePresets(): Array { const res = []; this.preset.children.forEach(preset => { - const editable = preset.createEditable(); + const editable = this.createEditable(preset, undefined, preset.getPath()); if (editable) { res.push(editable); } diff --git a/packages/survey-creator-core/src/presets/presets-base.ts b/packages/survey-creator-core/src/presets/presets-base.ts index 6004373df2..033f6feb7e 100644 --- a/packages/survey-creator-core/src/presets/presets-base.ts +++ b/packages/survey-creator-core/src/presets/presets-base.ts @@ -1,125 +1,10 @@ -import { EventBase, Helpers, SurveyModel } from "survey-core"; +import { EventBase } from "survey-core"; import { SurveyCreatorModel } from "../creator-base"; -export class CreatorPresetEditableBase { - public parent: CreatorPresetEditableBase; - public children: Array = []; - public constructor(public preset: CreatorPresetBase) { - preset.children.forEach(item => { - const editable = item.createEditable(); - if(!!editable) { - editable.parent = this; - this.children.push(editable); - } - }); - } - public get path() { return this.preset.getPath(); } - protected getJsonPath(model: SurveyModel): string { return this.path; } - public get fullPath() { - let prefix = this.parent ? this.parent.fullPath : ""; - if(this.path && prefix) { - prefix += "_"; - } - return prefix + this.path; - } - public get pageName(): string { return "page_" + this.fullPath; } - public createPages(): Array { - const res = []; - const mainPage = this.createMainPage(); - if(mainPage) { - res.push(mainPage); - } - this.children.forEach(item => { - const pages = item.createPages(); - if(Array.isArray(pages)) { - pages.forEach(page => res.push(page)); - } - }); - return res; - } - public validate(model: SurveyModel): boolean { - if(!this.validateCore(model)) return false; - for(let i = 0; i < this.children.length; i ++) { - if(!this.children[i].validate(model)) return false; - } - return true; - } - protected validateCore(model: SurveyModel): boolean { - return true; - } - protected createMainPage(): any { - const res = this.createMainPageCore(); - if(res) { - res.name = this.pageName; - } - return res; - } - protected getBoolVisibleIf(name: string): string { return "{" + name + "}=true"; } - protected getTextVisibleIf(name: string, val: string): string { return "{" + name + "}='" + val +"'"; } - protected getNotEmptyVisibleIf(name: string): string { return "{" + name + "} notempty"; } - protected createMainPageCore(): any { return undefined; } - public getJsonValue(model: SurveyModel): any { - const page = model.getPageByName(this.pageName); - const core = page && page.isVisible ? this.getJsonValueCore(model) : undefined; - let hasValue = !!core; - const res = hasValue ? core : {}; - this.children.forEach(item => { - const val = item.getJsonValue(model); - if(!!val) { - hasValue = true; - res[item.getJsonPath(model)] = val; - } - }); - return hasValue ? res : undefined; - } - public setupQuestions(model: SurveyModel, creator: SurveyCreatorModel): void { - this.setupQuestionsCore(model, creator); - this.children.forEach(item => { - item.setupQuestions(model, creator); - }); - } - public setupOnCurrentPage(model: SurveyModel, creator: SurveyCreatorModel): void { - if(model.currentPage.name === this.pageName) { - this.setupOnCurrentPageCore(model, creator); - } - this.children.forEach(item => { - item.setupOnCurrentPage(model, creator); - }); - } - public updateOnValueChanged(model: SurveyModel, creator: SurveyCreatorModel, name: string): void { - this.updateOnValueChangedCore(model, creator, name); - this.children.forEach(item => { - item.updateOnValueChanged(model, creator, name); - }); - } - public updateOnMatrixDetailPanelVisibleChanged(model: SurveyModel, creator: SurveyCreatorModel, options: any): void { - this.updateOnMatrixDetailPanelVisibleChangedCore(model, creator, options); - this.children.forEach(item => { - item.updateOnMatrixDetailPanelVisibleChanged(model, creator, options); - }); - } - public setupQuestionsValue(model: SurveyModel, json: any, creator: SurveyCreatorModel): void { - this.setupQuestionsValueCore(model, json, creator); - this.children.forEach(item => { - item.setupQuestionsValue(model, !!json ? json[item.path]: undefined, creator); - }); - } - protected setupQuestionsCore(model: SurveyModel, creator: SurveyCreatorModel): void { } - protected setupQuestionsValueCore(model: SurveyModel, json: any, creator: SurveyCreatorModel): void {} - protected getJsonValueCore(model: SurveyModel): any { return undefined; } - protected setupOnCurrentPageCore(model: SurveyModel, creator: SurveyCreatorModel): void {} - protected updateOnValueChangedCore(model: SurveyModel, creator: SurveyCreatorModel, name: string): void {} - protected updateOnMatrixDetailPanelVisibleChangedCore(model: SurveyModel, creator: SurveyCreatorModel, options: any): void {} - protected copyJson(json: any): any { - return Helpers.getUnbindValue(json); - } -} - export interface ICreatorPreset { setJson(json: any): void; apply(creator: SurveyCreatorModel): void; getPath(): string; - createEditable(): CreatorPresetEditableBase; } export abstract class CreatorPresetBase implements ICreatorPreset { @@ -140,7 +25,6 @@ export abstract class CreatorPresetBase implements ICreatorPreset { this.onApplied.fire(this, {}); } public abstract getPath(): string; - public createEditable(): CreatorPresetEditableBase { return undefined; } protected applyCore(creator: SurveyCreatorModel): void { } protected createPresets(): Array { return []; diff --git a/packages/survey-creator-core/src/presets/presets-properties.ts b/packages/survey-creator-core/src/presets/presets-properties.ts index 888faff13b..1293c042ee 100644 --- a/packages/survey-creator-core/src/presets/presets-properties.ts +++ b/packages/survey-creator-core/src/presets/presets-properties.ts @@ -1,406 +1,15 @@ -import { JsonObjectProperty, ItemValue, MatrixDropdownRowModelBase, QuestionDropdownModel, - QuestionMatrixDynamicModel, Base, Serializer, SurveyModel, ElementContentVisibilityChangedEvent, - matrixDropdownColumnTypes } from "survey-core"; -import { ICreatorPreset, CreatorPresetBase, CreatorPresetEditableBase } from "./presets-base"; +import { ICreatorPreset, CreatorPresetBase } from "./presets-base"; import { SurveyCreatorModel } from "../creator-base"; -import { defaultPropertyGridDefinition, ISurveyPropertyGridDefinition, ISurveyPropertiesDefinition } from "../question-editor/definition"; -import { SurveyQuestionProperties } from "../question-editor/properties"; -import { editorLocalization } from "../editorLocalization"; -import { PropertyGridModel } from "../../src/property-grid"; -import { QuestionEmbeddedSurveyModel } from "../components/embedded-survey"; - -export class SurveyQuestionPresetProperties extends SurveyQuestionProperties { - constructor(obj, className: string, propertyGridDefinition: ISurveyPropertyGridDefinition) { - super(obj, null, className, null, null, null, propertyGridDefinition); - } - protected getIsPropertyVisible(prop: JsonObjectProperty): boolean { - return prop.visible !== false; - } -} - -const presetPropertiesBaseClasses = ["question", "matrixdropdownbase", "selectbase", "panelbase", "matrixdropdowncolumn@default", "matrixdropdowncolumn@selectbase"]; - -export class SurveyQuestionPresetPropertiesDetail { - private propertiesHash = {}; - public classes = new Array(); - private properties: SurveyQuestionPresetProperties; - private propertyGridValue: PropertyGridModel; - private allPropertiesNames: Array; - constructor(private className: string, private currentJson: ISurveyPropertyGridDefinition) { - const cls = {}; - const obj = this.createObj(); - this.properties = new SurveyQuestionPresetProperties(obj, className, currentJson); - this.allPropertiesNames = this.properties.getAllVisiblePropertiesNames(true); - const objProps = {}; - Serializer.getPropertiesByObj(obj).forEach(prop => objProps[prop.name] = prop); - this.allPropertiesNames.forEach(name => { - const prop = objProps[name]; - if(prop) { - const propClassName = this.getPropClassName(prop); - this.propertiesHash[name] = propClassName; - cls[propClassName] = true; - } - }); - for(let i = 0; i < presetPropertiesBaseClasses.length; i ++) { - const cl = presetPropertiesBaseClasses[i]; - if(cls[cl]) { - this.classes.push(cl); - } - } - if(this.classes.indexOf(className) < 0) { - this.classes.push(className); - } - this.propertyGridValue = new PropertyGridModel(obj, undefined, this.currentJson); - } - private createObj(): Base { - if(this.className === "survey") return new SurveyModel(); - const ind = this.className.indexOf("@"); - if(ind < 0) return Serializer.createClass(this.className); - const clName = this.className.substring(0, ind); - const postFix = this.className.substring(ind + 1); - const res = Serializer.createClass(clName); - if(res.cellType) { - res.cellType = postFix; - } - return res; - } - public dispose(): void { - //TODO - } - public get propertyGrid(): PropertyGridModel { return this.propertyGridValue; } - public getRows(): Array { - const rows = []; - this.properties.getTabs().forEach(tab => { - const row: any = { name: tab.name, items: [] }; - tab.properties.forEach(prop => { - row.items.push(prop.name); - }); - rows.push(row); - }); - return rows; - } - public getRankingChoices(matrix: QuestionMatrixDynamicModel, row: MatrixDropdownRowModelBase): Array { - const val = matrix.value; - const usedItems = {}; - if(Array.isArray(val)) { - const rowIndex = matrix.visibleRows.indexOf(row); - for(let i = 0; i < val.length; i ++) { - const items = val[i].items; - if(i !== rowIndex && Array.isArray(items)) { - items.forEach(v => usedItems[v] = true); - } - } - } - const res = []; - this.allPropertiesNames.forEach(name => { - if(!usedItems[name]) { - res.push(new ItemValue(name, editorLocalization.getPropertyNameInEditor(this.className, name))); - } - }); - return res; - } - public updatePropertyGrid(val: Array): void { - const definition: ISurveyPropertyGridDefinition = { autoGenerateProperties: false, classes: {} }; - this.updateCurrentJsonCore(definition.classes, val); - this.propertyGrid.setPropertyGridDefinition(definition); - } - public updateCurrentJson(val: Array): void { - this.updateCurrentJsonCore(this.currentJson.classes, val); - } - private updateCurrentJsonCore(curJsonClasses: ISurveyPropertiesDefinition, val: Array): void { - if(!Array.isArray(val) || val.length === 0) return; - const tabNames = []; - this.classes.forEach(cl => { - this.updateCurrentJsonClass(curJsonClasses, val, cl, tabNames); - }); - } - private updateCurrentJsonClass(curJsonClasses: ISurveyPropertiesDefinition, val: Array, clName: string, tabNames: Array): void { - const properties = []; - const tabs = []; - const tabStep = 100; - - val.forEach(tab => { - const clVal = tab.items; - if(Array.isArray(clVal)) { - const classesIndeces = []; - this.classes.forEach(cl => classesIndeces.push(0)); - const propertiesIndeces = {}; - for(let i = 0; i < clVal.length; i ++) { - const clName = this.propertiesHash[clVal[i]]; - let clIndex = this.classes.indexOf(clName); - if(clIndex < 0) continue; - const nextStep = 10000 / Math.pow(10, clIndex); - let max = 0; - for(let j = 0; j <= clIndex; j ++) { - if(classesIndeces[j] > max) max = classesIndeces[j]; - } - const visIndex = max + nextStep; - propertiesIndeces[clVal[i]] = visIndex; - classesIndeces[clIndex] = visIndex; - } - clVal.forEach(propName => { - if(this.propertiesHash[propName] === clName) { - const tabName = tab.name !== "general" ? tab.name : undefined; - if(!!tabName && tabNames.indexOf(tab.name) < 0) { - tabNames.push(tab.name); - tabs.push({ name: tab.name, index: tabNames.length * tabStep }); - } - const item: any = { name: propName, index: propertiesIndeces[propName] }; - if(!!tabName) { - item.tab = tabName; - } - properties.push(item); - } - }); - } - }); - curJsonClasses[clName] = { properties: properties, tabs: tabs }; - } - private getPropClassName(prop: JsonObjectProperty): string { - const clName = prop.classInfo.name; - for(let i = 1; i < presetPropertiesBaseClasses.length; i ++) { - const cl = presetPropertiesBaseClasses[i]; - if(clName === cl || Serializer.isDescendantOf(clName, cl)) return this.getClassName(cl); - } - if(clName === this.className) return this.className; - return this.getClassName("question"); - } - private getClassName(className: string): string { - const ind = this.className.indexOf("@"); - if(ind < 0) return className; - const clName = this.className.substring(0, ind); - if(clName === className || className === "question") { - className = "default"; - } - return clName + "@" + className; - } -} - -export class CreatorPresetEditablePropertyGridDefinition extends CreatorPresetEditableBase { - private currentJson: ISurveyPropertyGridDefinition; - private currentProperties: SurveyQuestionPresetPropertiesDetail; - private currentClassName: string; - public createMainPageCore(): any { - const parent = (this.parent); - return { - title: "Property Grid categories", - elements: [ - { - type: "boolean", - name: this.nameShow, - title: "Do you want to configure Property Grid categories and properties?" - }, - { - type: "dropdown", - name: this.nameSelector, - visibleIf: this.getBoolVisibleIf(this.nameShow), - clearIfInvisible: "onHidden", - title: "Select element to setup a property grid for it", - }, - { - type: "panel", - name: "propPanel", - visibleIf: this.getNotEmptyVisibleIf(this.nameSelector), - elements: [ - { - type: "matrixdynamic", - name: this.nameMatrix, - allowRowsDragAndDrop: true, - showHeader: false, - titleLocation: "hidden", - addRowText: "Add New Category", - columns: [ - { cellType: "text", name: "name", title: "Category name", isUnique: true, isRequired: true, enableIf: "{row.name} <> 'general'" } - ], - detailPanelMode: "underRowSingle", - detailElements: [ - { - type: "ranking", - name: "items", - selectToRankEnabled: true, - selectToRankAreasLayout: "horizontal", - titleLocation: "hidden", - selectToRankEmptyRankedAreaText: "Drag properties to hide them", - selectToRankEmptyUnrankedAreaText: "Drag properties here" - } - ] - }, - { - type: "embeddedsurvey", - name: this.namePropertyGrid, - startWithNewLine: false - } - ] - } - ] - }; - } - public getJsonValueCore(model: SurveyModel): any { - if(model.getValue(this.nameShow) !== true) return undefined; - this.updateCurrentJson(model); - return this.currentJson; - } - protected setupQuestionsCore(model: SurveyModel, creator: SurveyCreatorModel): void { - this.getSelector(model).choices = this.getSelectorChoices(creator); - this.getMatrix(model).lockedRowCount = 1; - } - protected updateOnMatrixDetailPanelVisibleChangedCore(model: SurveyModel, creator: SurveyCreatorModel, options: any): void { - if(options.question.name === this.nameMatrix) { - this.onDetailPanelShowingChanged(model, options.row); - this.expandEmbeddedSurveyPanel(model); - } - } - private isMatrixValueChanged: boolean; - private isMatrixValueSetting: boolean; - protected updateOnValueChangedCore(model: SurveyModel, creator: SurveyCreatorModel, name: string): void { - if(name === this.nameMatrix && !this.isMatrixValueSetting) { - this.isMatrixValueChanged = true; - if(this.currentProperties) { - this.currentProperties.updatePropertyGrid(model.getValue(name)); - this.updateEmbeddedSurvey(model); - } - } - if(name !== this.nameSelector) return; - this.isMatrixValueSetting = true; - this.updateCurrentJson(model); - if(this.currentProperties) { - this.currentProperties.dispose(); - this.currentProperties = null; - } - const selQuestion = this.getSelector(model); - this.currentClassName = selQuestion.value; - if(!this.currentClassName) return; - const matrix = this.getMatrix(model); - this.currentProperties = new SurveyQuestionPresetPropertiesDetail(this.currentClassName, this.currentJson); - matrix.rowCount = 0; - matrix.value = this.currentProperties.getRows(); - this.updateEmbeddedSurvey(model); - this.isMatrixValueChanged = false; - this.isMatrixValueSetting = false; - } - private updateEmbeddedSurvey(model: SurveyModel): void { - const propGridQuestion = this.getPropertyGridQuestion(model); - const survey = this.currentProperties.propertyGrid.survey; - propGridQuestion.embeddedSurvey = survey; - this.expandEmbeddedSurveyPanel(model); - survey.onElementContentVisibilityChanged.add((sender, options) => { - this.onElementContentVisibilityChanged(model, options); - }); - } - private isContentVisibilityChanging: boolean; - private expandEmbeddedSurveyPanel(model: SurveyModel): void { - if(this.isContentVisibilityChanging) return; - const propGridSurvey = this.currentProperties.propertyGrid.survey; - if(!propGridSurvey) return; - const matrix = this.getMatrix(model); - let name = ""; - matrix.visibleRows.forEach(row => { - if(row.isDetailPanelShowing) { - name = row.getValue("name"); - } - }); - const panel = !!name ? propGridSurvey.getPanelByName(name) : undefined; - this.isContentVisibilityChanging = true; - if(panel) { - panel.expand(); - } else { - propGridSurvey.getAllPanels(true).forEach(panel => panel.collapse()); - } - this.isContentVisibilityChanging = false; - } - private onElementContentVisibilityChanged(model: SurveyModel, options: ElementContentVisibilityChangedEvent): void { - if(this.isContentVisibilityChanging) return; - this.isContentVisibilityChanging = true; - const matrix = this.getMatrix(model); - if(matrix) { - const isExpand = options.element.isExpanded; - const name = options.element.name; - matrix.visibleRows.forEach(row => { - if(isExpand && row.getValue("name") === name) { - row.showDetailPanel(); - } else { - row.hideDetailPanel(); - } - }); - } - this.isContentVisibilityChanging = false; - } - protected setupQuestionsValueCore(model: SurveyModel, json: any, creator: SurveyCreatorModel): void { - model.setValue(this.nameShow, !!json); - if(!json) { - json = this.copyJson(defaultPropertyGridDefinition); - } - this.currentJson = json; - this.currentJson.autoGenerateProperties = false; - } - private get nameShow() { return this.fullPath + "_show"; } - private getMatrix(model: SurveyModel): QuestionMatrixDynamicModel { return model.getQuestionByName(this.nameMatrix); } - private getSelector(model: SurveyModel): QuestionDropdownModel { return model.getQuestionByName(this.nameSelector); } - private getPropertyGridQuestion(model: SurveyModel): QuestionEmbeddedSurveyModel { return model.getQuestionByName(this.namePropertyGrid); } - private get nameMatrix() { return this.fullPath + "_matrix"; } - private get nameSelector() { return this.fullPath + "_selector"; } - private get namePropertyGrid() { return this.fullPath + "_propgrid"; } - private onDetailPanelShowingChanged(model: SurveyModel, row: MatrixDropdownRowModelBase): void { - if(!row.isDetailPanelShowing || !this.currentProperties) return; - const classes = this.currentProperties.classes; - const matrix = this.getMatrix(model); - const q = row.detailPanel.getQuestionByName("items"); - q.choices = this.currentProperties.getRankingChoices(matrix, row); - } - private getSelectorChoices(creator: SurveyCreatorModel): Array { - const classes = ["survey", "page", "panel"]; - const toolboxItems = {}; - creator.toolbox.getDefaultItems([], false, true, true).forEach(item => { - toolboxItems[item.id] = true; - }); - - Serializer.getChildrenClasses("question", true).forEach(cl => { - if(toolboxItems[cl.name]) { - classes.push(cl.name); - } - }); - const res = []; - classes.forEach(str => res.push(new ItemValue(str, this.getSelectorItemTitle(str)))); - const columnPrefix = "matrixdropdowncolumn@"; - res.push(new ItemValue(columnPrefix + "default", this.getColumnItemTitle(""))); - for(let key in matrixDropdownColumnTypes) { - res.push(new ItemValue(columnPrefix + key, this.getColumnItemTitle(key))); - } - return res; - } - private getSelectorItemTitle(name: string): string { - if(name === "survey") return editorLocalization.getString("ed.surveyTypeName"); - if(name === "page") return editorLocalization.getString("ed.pageTypeName"); - return editorLocalization.getString("qt." + name); - } - private getColumnItemTitle(name: string): string { - const columnTitle = editorLocalization.getString("ed.columnTypeName"); - const postFix = !name ? "default" : this.getSelectorItemTitle(name); - return columnTitle + ": " + postFix; - } - private updateCurrentJson(model: SurveyModel): void { - if(!this.isMatrixValueChanged) return; - this.isMatrixValueChanged = false; - if(this.currentProperties) { - this.currentProperties.updateCurrentJson(model.getValue(this.nameMatrix)); - } - } -} export class CreatorPresetPropertyGridDefinition extends CreatorPresetBase { public getPath(): string { return "definition"; } - public createEditable(): CreatorPresetEditableBase { return new CreatorPresetEditablePropertyGridDefinition(this); } protected applyCore(creator: SurveyCreatorModel): void { creator.setPropertyGridDefinition(this.json); } } -export class CreatorEditablePresetPropertyGrid extends CreatorPresetEditableBase { -} - export class CreatorPresetPropertyGrid extends CreatorPresetBase { public getPath(): string { return "propertyGrid"; } - public createEditable(): CreatorPresetEditableBase { return new CreatorEditablePresetPropertyGrid(this); } protected createPresets(): Array { return [new CreatorPresetPropertyGridDefinition()]; } diff --git a/packages/survey-creator-core/src/presets/presets-tabs.ts b/packages/survey-creator-core/src/presets/presets-tabs.ts index 922b68857c..6e25bb5e64 100644 --- a/packages/survey-creator-core/src/presets/presets-tabs.ts +++ b/packages/survey-creator-core/src/presets/presets-tabs.ts @@ -1,81 +1,8 @@ -import { ItemValue, Question, SurveyModel } from "survey-core"; -import { CreatorPresetBase, CreatorPresetEditableBase } from "./presets-base"; +import { CreatorPresetBase } from "./presets-base"; import { SurveyCreatorModel } from "../creator-base"; -import { editorLocalization } from "../editorLocalization"; - -export class CreatorPresetEditableTabs extends CreatorPresetEditableBase { - public createMainPageCore(): any { - const nameShow = this.nameShow; - const visibleIf = this.getBoolVisibleIf(nameShow); - return { - title: "Tabs customization", - elements: [ - { - type: "boolean", - name: nameShow, - title: "Do you want to setup Creator tabs?", - }, - { - type: "ranking", - name: this.nameItems, - title: "Please order the Creator tabs", - selectToRankEnabled: true, - minSelectedChoices: 1, - selectToRankAreasLayout: "vertical", - visibleIf: visibleIf - }, - { - type: "dropdown", - name: this.nameActiveTab, - title: "Select the default active tab (the first tab is active if this field is empty)", - choicesFromQuestion: this.nameItems, - choicesFromQuestionMode: "selected", - visibleIf: visibleIf - } - ] - }; - } - protected getJsonValueCore(model: SurveyModel): any { - if(!model.getValue(this.nameShow)) return undefined; - const items = model.getValue(this.nameItems); - if(!Array.isArray(items)) return undefined; - const val: any = { items: items }; - const activeTab = model.getValue(this.nameActiveTab); - if(activeTab) { - val.activeTab = activeTab; - } - return val; - } - protected setupQuestionsCore(model: SurveyModel, creator: SurveyCreatorModel): void { - const q = model.getQuestionByName(this.nameItems); - if (q) { - const choices = []; - creator.getAvailableTabNames().forEach(tab => choices.push(new ItemValue(tab, this.getTabTitle(tab)))); - - q.choices = choices; - } - } - protected setupQuestionsValueCore(model: SurveyModel, json: any, creator: SurveyCreatorModel): void { - json = json || {}; - const items = json["items"] || []; - model.setValue(this.nameShow, items.length > 0); - model.setValue(this.nameItems, items.length > 0 ? items : creator.getTabNames()); - model.setValue(this.nameActiveTab, json["activeTab"] || creator.activeTab); - } - private getTabTitle(name: string): string { - if(name === "preview") name = "testSurvey"; - if(name === "editor") name = "jsonEditor"; - if(name === "theme") name = "themeSurvey"; - return editorLocalization.getString("ed." + name); - } - private get nameShow() { return this.path + "_show"; } - private get nameItems() { return this.path + "_items"; } - private get nameActiveTab() { return this.path + "_activeTab"; } -} export class CreatorPresetTabs extends CreatorPresetBase { public getPath(): string { return "tabs"; } - public createEditable(): CreatorPresetEditableBase { return new CreatorPresetEditableTabs(this); } protected applyCore(creator: SurveyCreatorModel): void { super.applyCore(creator); this.applyTabs(creator, this.json["items"]); diff --git a/packages/survey-creator-core/src/presets/presets-toolbox.ts b/packages/survey-creator-core/src/presets/presets-toolbox.ts index a77a57ceda..f9d20952aa 100644 --- a/packages/survey-creator-core/src/presets/presets-toolbox.ts +++ b/packages/survey-creator-core/src/presets/presets-toolbox.ts @@ -1,8 +1,6 @@ -import { ItemValue, MatrixDropdownRowModelBase, Question, QuestionMatrixDynamicModel, QuestionRankingModel, Serializer, SurveyModel } from "survey-core"; -import { ICreatorPreset, CreatorPresetBase, CreatorPresetEditableBase } from "./presets-base"; +import { ICreatorPreset, CreatorPresetBase } from "./presets-base"; import { SurveyCreatorModel } from "../creator-base"; import { IQuestionToolboxItem, IToolboxCategoryDefinition } from "../toolbox"; -import { SurveyJSON5 } from "../json5"; export interface ICreatorPresetToolboxItem { name: string; @@ -12,133 +10,12 @@ export interface ICreatorPresetToolboxItem { tooltip?: string; } -export class CreatorPresetEditableToolboxDefinition extends CreatorPresetEditableBase { - public createMainPageCore(): any { - return { - title: "Toolbox items definition", - elements: [ - { - type: "boolean", - name: this.nameShow, - title: "Update existing toolbox items and create new items" - }, - { - type: "matrixdynamic", - name: this.nameMatrix, - visibleIf: this.getBoolVisibleIf(this.nameShow), - title: "Define items definition", - rowCount: 0, - addRowText: "Add New Item Defintion", - columns: [ - { cellType: "text", name: "name", title: "Name", isUnique: true, isRequired: true }, - { cellType: "text", name: "iconName", title: "Icon Name" }, - { cellType: "text", name: "title", title: "Title" } - ], - detailPanelMode: "underRow", - detailElements: [ - { type: "text", name: "tooltip", title: "Tooltip" }, - { type: "comment", name: "json", title: "JSON that will be used on clicking item", rows: 15 } - ] - } - ] - }; - } - protected setupQuestionsCore(model: SurveyModel, creator: SurveyCreatorModel): void { - const matrix = this.getMatrix(model); - const nameColumn = matrix.getColumnByName("name"); - const iconNameColumn = matrix.getColumnByName("iconName"); - const names = []; - const iconNames = []; - creator.toolbox.getDefaultItems([], false, true, true).forEach(item => { - names.push(item.id); - iconNames.push(item.iconName || ("icon-" + item.id)); - }); - names.sort(); - iconNames.sort(); - nameColumn["dataList"] = names; - iconNameColumn["dataList"] = iconNames; - } - protected validateCore(model: SurveyModel): boolean { - const matrix = this.getMatrix(model); - const val = matrix.value; - if(!Array.isArray(val)) return true; - for(let rowIndex = 0; rowIndex < val.length; rowIndex ++) { - const json = val[rowIndex]["json"]; - if(!!json) { - if(!this.validateJson(json)) { - const row = matrix.visibleRows[rowIndex]; - row.showDetailPanel(); - const jsonQuestion = row.getQuestionByName("json"); - jsonQuestion.addError("The json is invalid"); // - jsonQuestion.focus(); - return false; - } - } - } - return true; - } - public getJsonValueCore(model: SurveyModel): any { - const matrix = this.getMatrix(model); - const value = matrix.value; - if(!Array.isArray(value) || value.length === 0) return undefined; - const res = []; - for(let i = 0; i < value.length; i ++) { - const val = {}; - const item = value[i]; - for(let key in item) { - const itemVal = key === "json" ? this.parseJson(item[key]) : item[key]; - if(!!itemVal) { - val[key] = itemVal; - } - } - res.push(val); - } - return res; - } - protected setupQuestionsValueCore(model: SurveyModel, json: any, creator: SurveyCreatorModel): void { - model.setValue(this.nameShow, !!json); - json = json || []; - const question = this.getMatrix(model); - const value = []; - json.forEach(item => { - const val = {}; - for(let key in item) { - val[key] = key === "json" ? JSON.stringify(item[key], null, 2) : item[key]; - } - value.push(val); - }); - question.value = value; - } - private getMatrix(model: SurveyModel): QuestionMatrixDynamicModel { - return model.getQuestionByName(this.nameMatrix); - } - private get nameMatrix() { return this.fullPath + "_matrix"; } - public get nameShow() { return this.fullPath + "_show"; } - private validateJson(text: string): boolean { - text = text.trim(); - if(!text) return true; - const json = this.parseJson(text); - if(!json || !json.type) return false; - const obj = Serializer.createClass(json.type, json); - return !!obj; - } - private parseJson(text: string): any { - try { - const res = new SurveyJSON5().parse(text); - return res; - } catch(e) { - return undefined; - } - } -} - export class CreatorPresetToolboxDefinition extends CreatorPresetBase { public getPath(): string { return "definition"; } protected applyCore(creator: SurveyCreatorModel): void { super.applyCore(creator); this.applyDefinition(creator, this.json); } - public createEditable(): CreatorPresetEditableBase { return new CreatorPresetEditableToolboxDefinition(this); } private applyDefinition(creator: SurveyCreatorModel, defintion: Array): void { if (!Array.isArray(defintion)) return; const tb = creator.toolbox; @@ -160,174 +37,8 @@ export class CreatorPresetToolboxDefinition extends CreatorPresetBase { } } -export class CreatorPresetEditableToolboxConfigurator extends CreatorPresetEditableBase { - private defaultItems: ItemValue[]; - public createMainPageCore(): any { - return { - title: "Setup toolbox", - elements: [ - { - type: "boolean", - name: this.nameCategoriesShow, - title: "Setup toolbox items and categories" - }, - { - type: "buttongroup", - name: this.nameCategoriesMode, - title: "Do you want to have categories or plain items", - defaultValue: "categories", - choices: [{ value: "categories", text: "Categories" }, { value: "items", text: "No categories" }], - visibleIf: this.getBoolVisibleIf(this.nameCategoriesShow), - clearIfInvisible: "onHidden", - }, - { - type: "matrixdynamic", - name: this.nameCategories, - visibleIf: this.getTextVisibleIf(this.nameCategoriesMode, "categories"), - minRowCount: 1, - allowRowsDragAndDrop: true, - showHeader: false, - columns: [ - { cellType: "text", name: "name", title: "Category Name", isUnique: true, isRequired: true }, - { cellType: "expression", name: "count", title: "Number of items in category", expression: "{row.items.length}" } - ], - detailPanelMode: "underRowSingle", - detailElements: [ - { - type: "ranking", - name: "items", - titleLocation: "hidden", - selectToRankEnabled: true, - minSelectedChoices: 1, - selectToRankAreasLayout: "horizontal" - } - ] - }, - { - type: "ranking", - name: this.nameItems, - visibleIf: this.getTextVisibleIf(this.nameCategoriesMode, "items"), - titleLocation: "hidden", - selectToRankEnabled: true, - minSelectedChoices: 1, - selectToRankAreasLayout: "horizontal", - } - ] - }; - } - public get nameCategoriesShow() { return this.fullPath + "_show"; } - public get nameCategoriesMode() { return this.fullPath + "_mode"; } - private get nameItems() { return this.fullPath + "_items"; } - private get nameCategories() { return this.fullPath + "_categories"; } - protected getJsonPath(model: SurveyModel): string { - return model.getValue(this.nameCategoriesMode); - } - public getJsonValueCore(model: SurveyModel): any { - const mode = model.getValue(this.nameCategoriesMode); - if(mode === "items") return model.getValue(this.nameItems); - if(mode === "categories") return this.getCategoriesJson(model); - } - private getCategoriesJson(model: SurveyModel): any { - const res = model.getValue(this.nameCategories); - if(!Array.isArray(res)) return undefined; - res.forEach(item => { - delete item["count"]; - }); - return res; - } - protected updateOnMatrixDetailPanelVisibleChangedCore(model: SurveyModel, creator: SurveyCreatorModel, options: any): void { - if(options.question.name === this.nameCategories) { - this.onDetailPanelShowingChanged(options.row); - } - } - protected setupOnCurrentPageCore(model: SurveyModel, creator: SurveyCreatorModel): void { - this.defaultItems = this.getDefaultToolboxItems(model, creator); - this.getQuestionItems(model).choices = this.defaultItems; - } - protected setupQuestionsCore(model: SurveyModel, creator: SurveyCreatorModel): void { - this.defaultItems = this.getDefaultToolboxItems(model, creator); - this.getQuestionItems(model).choices = this.defaultItems; - } - protected setupQuestionsValueCore(model: SurveyModel, json: any, creator: SurveyCreatorModel): void { - const val = []; - creator.toolbox.items.forEach(item => val.push(item.id)); - this.getQuestionItems(model).value = val; - const nameCategories = {}; - const categories = []; - creator.toolbox.items.forEach(item => { - const category = item.category; - if(!!category) { - if(!nameCategories[category]) { - const row = { name: category, items: [item.name] }; - nameCategories[category] = row; - categories.push(row); - } else { - nameCategories[category].items.push(item.name); - } - } - }); - model.setValue(this.nameCategories, categories); - this.getQuestionCategories(model).visibleRows.forEach(row => { - row.onDetailPanelShowingChanged = () => { - this.onDetailPanelShowingChanged(row); - }; - }); - } - private getQuestionItems(model: SurveyModel): QuestionRankingModel { - return model.getQuestionByName(this.nameItems); - } - private getQuestionCategories(model: SurveyModel): QuestionMatrixDynamicModel { return model.getQuestionByName(this.nameCategories); } - private onDetailPanelShowingChanged(row: MatrixDropdownRowModelBase): void { - if(!row.isDetailPanelShowing) return; - row.getQuestionByName("items").choices = this.getRankingChoices(row); - } - private getDefaultToolboxItems(model: SurveyModel, creator: SurveyCreatorModel): ItemValue[] { - const items = {}; - creator.toolbox.getDefaultItems([], false, true, true).forEach(item => { - items[item.id] = item.title; - }); - const definitionVal = model.getValue("toolbox_definition_matrix"); - if(Array.isArray(definitionVal)) { - definitionVal.forEach(item => { - const key = item.name; - if(!!key && !items[key] || !!item.title) { - items[key] = item.title || key; - } - }); - } - const res = []; - for(let key in items) { - res.push(new ItemValue(key, items[key])); - } - - return res; - } - private getRankingChoices(row: MatrixDropdownRowModelBase): Array { - const res = []; - const model = row.getSurvey(); - const matrix = this.getQuestionCategories(model); - if(!Array.isArray(this.defaultItems)) return res; - const val = model.getValue(this.nameCategories); - const usedItems = {}; - if(Array.isArray(val)) { - const rowIndex = matrix.visibleRows.indexOf(row); - for(let i = 0; i < val.length; i ++) { - if(i !== rowIndex && Array.isArray(val[i].items)) { - val[i].items.forEach(v => usedItems[v] = true); - } - } - } - this.defaultItems.forEach(item => { - if(!usedItems[item.id]) { - res.push(new ItemValue(item.id, item.title)); - } - }); - return res; - } -} export class CreatorPresetToolboxConfigurator extends CreatorPresetBase { public getPath(): string { return ""; } - public createEditable(): CreatorPresetEditableBase { return new CreatorPresetEditableToolboxConfigurator(this); } protected applyCore(creator: SurveyCreatorModel): void { if(!this.json) return; super.applyCore(creator); @@ -345,11 +56,8 @@ export class CreatorPresetToolboxConfigurator extends CreatorPresetBase { creator.toolbox.hasCategories = true; } } -export class CreatorPresetEditableToolbox extends CreatorPresetEditableBase {} - export class CreatorPresetToolbox extends CreatorPresetBase { public getPath(): string { return "toolbox"; } - public createEditable(): CreatorPresetEditableBase { return new CreatorPresetEditableToolbox(this); } protected createPresets(): Array { return [new CreatorPresetToolboxDefinition(), new CreatorPresetToolboxConfigurator()]; } diff --git a/packages/survey-creator-core/tests/presets-editor.tests.ts b/packages/survey-creator-core/tests/presets-editor.tests.ts index 0a9d8113bd..7ac045bd3f 100644 --- a/packages/survey-creator-core/tests/presets-editor.tests.ts +++ b/packages/survey-creator-core/tests/presets-editor.tests.ts @@ -1,11 +1,11 @@ import { CreatorTester } from "./creator-tester"; -import { CreatorPreset, ICreatorPresetData } from "../src/presets/presets"; +import { ICreatorPresetData } from "../src/presets/presets"; import { QuestionToolbox } from "../src/toolbox"; import { ItemValue, QuestionDropdownModel, QuestionMatrixDynamicModel } from "survey-core"; -import { defaultPropertyGridDefinition, ISurveyPropertyGridDefinition } from "../src/question-editor/definition"; -import { SurveyQuestionPresetPropertiesDetail } from "../src/presets/presets-properties"; +import { defaultPropertyGridDefinition } from "../src/question-editor/definition"; +import { SurveyQuestionPresetPropertiesDetail } from "../src/presets/editable/presets-editable-properties"; import { QuestionEmbeddedSurveyModel } from "../src/components/embedded-survey"; -import { CreatorPresetEditor } from "../src/presets/presets-editor"; +import { CreatorPresetEditor } from "../src/presets/editable/presets-editor"; export * from "../src/components/embedded-survey";