Skip to content

Commit

Permalink
Custom specialized question types do not inherit properties from the … (
Browse files Browse the repository at this point in the history
#7690)

* Custom specialized question types do not inherit properties from the base question type
fix #7661

* Add getDynamicType function #7661
  • Loading branch information
andrewtelnov authored Jan 18, 2024
1 parent 07ded5a commit ac5dfd1
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 73 deletions.
93 changes: 52 additions & 41 deletions src/jsonobject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1051,42 +1051,58 @@ export class JsonMetadata {
}
public getPropertiesByObj(obj: any): Array<JsonObjectProperty> {
if (!obj || !obj.getType) return [];
var res: any = {};
var props = this.getProperties(obj.getType());
for (var i = 0; i < props.length; i++) {
res[props[i].name] = props[i];
}
var dynamicProps = !!obj.getDynamicType
? this.getProperties(obj.getDynamicType())
: null;
if (dynamicProps && dynamicProps.length > 0) {
for (var i = 0; i < dynamicProps.length; i++) {
let dProp = dynamicProps[i];
if (!!res[dProp.name]) continue;
res[dProp.name] = dProp;
const props = this.getProperties(obj.getType());
const dynamicProps = this.getDynamicPropertiesByObj(obj);
return [].concat(props).concat(dynamicProps);
}
public addDynamicPropertiesIntoObj(dest: any, src: any, props: Array<JsonObjectProperty>): void {
props.forEach(prop => {
this.addDynamicPropertyIntoObj(dest, src, prop.name, false);
if (prop.serializationProperty) {
this.addDynamicPropertyIntoObj(dest, src, prop.serializationProperty, true);
}
if (prop.alternativeName) {
this.addDynamicPropertyIntoObj(dest, src, prop.alternativeName, false);
}
});
}
private addDynamicPropertyIntoObj(dest: any, src: any, propName: string, isReadOnly: boolean): void {
var desc = {
configurable: true,
get: function () {
return src[propName];
},
};
if (!isReadOnly) {
(<any>desc)["set"] = function (v: any) {
src[propName] = v;
};
}
return Object.keys(res).map((key) => res[key]);
Object.defineProperty(dest, propName, desc);
}
public getDynamicPropertiesByObj(obj: any, dynamicType: string = null): Array<JsonObjectProperty> {
if (!obj || !obj.getType || (!obj.getDynamicType && !dynamicType))
return [];
const objType = obj.getType();
if (!obj || !obj.getType) return [];
if(!!obj.getDynamicProperties) return obj.getDynamicProperties();
if(!obj.getDynamicType && !dynamicType) return [];
const dType = !!dynamicType ? dynamicType : obj.getDynamicType();
if (!dType) return [];
const cacheType = dType + "-" + objType;
return this.getDynamicPropertiesByTypes(obj.getType(), dType);
}
public getDynamicPropertiesByTypes(objType: string, dynamicType: string, invalidNames?: Array<string>): Array<JsonObjectProperty> {
if (!dynamicType) return [];
const cacheType = dynamicType + "-" + objType;
if(this.dynamicPropsCache[cacheType]) return this.dynamicPropsCache[cacheType];
var dynamicProps = this.getProperties(dType);
var dynamicProps = this.getProperties(dynamicType);
if (!dynamicProps || dynamicProps.length == 0) return [];
var hash: any = {};
var props = this.getProperties(objType);
const hash: any = {};
const props = this.getProperties(objType);
for (var i = 0; i < props.length; i++) {
hash[props[i].name] = props[i];
}
var res = [];
for (var i = 0; i < dynamicProps.length; i++) {
let dProp = dynamicProps[i];
if (!hash[dProp.name]) {
const res = [];
if(!invalidNames) invalidNames = [];
for (let i = 0; i < dynamicProps.length; i++) {
const dProp = dynamicProps[i];
if (!hash[dProp.name] && invalidNames.indexOf(dProp.name) < 0) {
res.push(dProp);
}
}
Expand Down Expand Up @@ -1630,23 +1646,18 @@ export class JsonObject {
private addDynamicProperties(
obj: any,
jsonObj: any,
properties: Array<JsonObjectProperty>
props: Array<JsonObjectProperty>
): Array<JsonObjectProperty> {
if (!obj.getDynamicPropertyName) return properties;
var dynamicPropName = obj.getDynamicPropertyName();
if (!dynamicPropName) return properties;
if (jsonObj[dynamicPropName]) {
obj[dynamicPropName] = jsonObj[dynamicPropName];
}
var dynamicProperties = this.getDynamicProperties(obj);
var res = [];
for (var i = 0; i < properties.length; i++) {
res.push(properties[i]);
}
for (var i = 0; i < dynamicProperties.length; i++) {
res.push(dynamicProperties[i]);
if (!obj.getDynamicPropertyName && !obj.getDynamicProperties) return props;
if(obj.getDynamicPropertyName) {
const dynamicPropName = obj.getDynamicPropertyName();
if (!dynamicPropName) return props;
if (dynamicPropName && jsonObj[dynamicPropName]) {
obj[dynamicPropName] = jsonObj[dynamicPropName];
}
}
return res;
const dynamicProps = this.getDynamicProperties(obj);
return dynamicProps.length === 0 ? props : [].concat(props).concat(dynamicProps);
}
private propertiesToJson(
obj: any,
Expand Down
48 changes: 46 additions & 2 deletions src/question_custom.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Question, IConditionObject } from "./question";
import { Serializer, CustomPropertiesCollection } from "./jsonobject";
import { Serializer, CustomPropertiesCollection, JsonObjectProperty } from "./jsonobject";
import {
ISurveyImpl,
ISurveyData,
Expand Down Expand Up @@ -82,6 +82,7 @@ export interface ICustomQuestionTypeConfiguration {
* ```
*/
defaultQuestionTitle?: any;
inheritBaseProps?: false | true | Array<string>;
/**
* A function that is called when the custom question is created. Use it to access questions nested within a [composite question type](https://surveyjs.io/form-library/documentation/customize-question-types/create-composite-question-types).
*
Expand Down Expand Up @@ -239,6 +240,7 @@ export interface ICustomQuestionTypeConfiguration {
}

export class ComponentQuestionJSON {
private dynamicProperties: Array<JsonObjectProperty>;
public constructor(public name: string, public json: ICustomQuestionTypeConfiguration) {
var self = this;
Serializer.addClass(
Expand Down Expand Up @@ -331,6 +333,34 @@ export class ComponentQuestionJSON {
public get isComposite(): boolean {
return !!this.json.elementsJSON || !!this.json.createElements;
}
public getDynamicProperties(): Array<JsonObjectProperty> {
if(!Array.isArray(this.dynamicProperties)) {

}
this.dynamicProperties = this.calcDynamicProperties();
return this.dynamicProperties;
}
private calcDynamicProperties(): Array<JsonObjectProperty> {
const baseProps = this.json.inheritBaseProps;
if(!baseProps || !this.json.questionJSON) return [];
const type = this.json.questionJSON.type;
if(!type) return [];
if(Array.isArray(baseProps)) {
const props: Array<JsonObjectProperty> = [];
baseProps.forEach(name => {
const prop = Serializer.findProperty(type, name);
if(prop) {
props.push(prop);
}
});
return props;
}
const invalidNames = [];
for(let key in this.json.questionJSON) {
invalidNames.push(key);
}
return Serializer.getDynamicPropertiesByTypes(this.name, type, invalidNames);
}
}

export class ComponentCollection {
Expand Down Expand Up @@ -681,8 +711,15 @@ export class QuestionCustomModel extends QuestionCustomModelBase {
public getTemplate(): string {
return "custom";
}
protected createWrapper() {
public getDynamicProperties(): Array<JsonObjectProperty> {
return this.customQuestion.getDynamicProperties() || [];
}
public getDynamicType(): string {
return this.questionWrapper ? this.questionWrapper.getType() : "question";
}
protected createWrapper(): void {
this.questionWrapper = this.createQuestion();
this.createDynamicProperties(this.questionWrapper);
}
protected getElement(): SurveyElement {
return this.contentQuestion;
Expand Down Expand Up @@ -842,6 +879,13 @@ export class QuestionCustomModel extends QuestionCustomModelBase {
}
this.isSettingValueChanged = false;
}
private createDynamicProperties(el: SurveyElement): void {
if(!el) return;
const props = this.getDynamicProperties();
if(Array.isArray(props)) {
Serializer.addDynamicPropertiesIntoObj(this, el, props);
}
}
protected initElement(el: SurveyElement): void {
super.initElement(el);
if (!!el) {
Expand Down
32 changes: 2 additions & 30 deletions src/question_matrixdropdowncolumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,36 +838,8 @@ export class MatrixDropdownColumn extends Base
}
}
private addProperties(curCellType: string) {
var question = this.templateQuestion;
var properties = this.getProperties(curCellType);
for (var i = 0; i < properties.length; i++) {
var prop = properties[i];
this.addProperty(question, prop.name, false);
if (prop.serializationProperty) {
this.addProperty(question, prop.serializationProperty, true);
}
if (prop.alternativeName) {
this.addProperty(question, prop.alternativeName, false);
}
}
}
private addProperty(
question: Question,
propName: string,
isReadOnly: boolean
) {
var desc = {
configurable: true,
get: function () {
return (<any>question)[propName];
},
};
if (!isReadOnly) {
(<any>desc)["set"] = function (v: any) {
(<any>question)[propName] = v;
};
}
Object.defineProperty(this, propName, desc);
const props = this.getProperties(curCellType);
Serializer.addDynamicPropertiesIntoObj(this, this.templateQuestion, props);
}
}

Expand Down
83 changes: 83 additions & 0 deletions tests/question_customtests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2730,3 +2730,86 @@ QUnit.test("composite component: defaultQuestionTitle", function (assert) {

ComponentCollection.Instance.clear();
});
QUnit.test("single component: inheritBaseProps: array<string>", function (assert) {
ComponentCollection.Instance.add({
name: "customdropdown",
inheritBaseProps: ["allowClear", "showOtherItem"],
questionJSON: {
type: "dropdown",
choices: [1, 2, 3]
},
});

const survey = new SurveyModel({
elements: [
{ type: "customdropdown", name: "q1", allowClear: false, showOtherItem: true }
]
});
const q1 = <QuestionCustomModel>survey.getQuestionByName("q1");
const content = <QuestionDropdownModel>q1.contentQuestion;
assert.equal(q1.allowClear, false, "q1.allowClear #1");
assert.equal(content.allowClear, false, "content.allowClear #1");
q1.allowClear = true;
assert.equal(q1.allowClear, true, "q1.allowClear #2");
assert.equal(content.allowClear, true, "content.allowClear #2");
content.allowClear = false;
assert.equal(q1.allowClear, false, "q1.allowClear #3");
assert.equal(content.allowClear, false, "content.allowClear #3");

assert.equal(q1.showOtherItem, true, "q1.showOtherItem #1");
assert.equal(content.showOtherItem, true, "content.showOtherItem #1");
q1.showOtherItem = false;
assert.equal(q1.showOtherItem, false, "q1.showOtherItem #2");
assert.equal(content.showOtherItem, false, "content.showOtherItem #2");
content.showOtherItem = true;
assert.equal(q1.showOtherItem, true, "q1.showOtherItem #3");
assert.equal(content.showOtherItem, true, "content.showOtherItem #3");
const json = q1.toJSON();
assert.equal(json.allowClear, false, "json.allowClear");
assert.equal(json.showOtherItem, true, "json.showOtherItem");

ComponentCollection.Instance.clear();
});
QUnit.test("single component: inheritBaseProps: true", function (assert) {
ComponentCollection.Instance.add({
name: "customdropdown",
inheritBaseProps: true,
questionJSON: {
type: "dropdown",
choices: [1, 2, 3]
},
});

const survey = new SurveyModel({
elements: [
{ type: "customdropdown", name: "q1", allowClear: false, showOtherItem: true }
]
});
const q1 = <QuestionCustomModel>survey.getQuestionByName("q1");
const content = <QuestionDropdownModel>q1.contentQuestion;
assert.equal(q1.getDynamicType(), "dropdown", "q1.getDynamicType()");
assert.equal(content.choices.length, 3, "content.choices");
assert.notOk(q1.choices, "q1.choices");
assert.equal(q1.allowClear, false, "q1.allowClear #1");
assert.equal(content.allowClear, false, "content.allowClear #1");
q1.allowClear = true;
assert.equal(q1.allowClear, true, "q1.allowClear #2");
assert.equal(content.allowClear, true, "content.allowClear #2");
content.allowClear = false;
assert.equal(q1.allowClear, false, "q1.allowClear #3");
assert.equal(content.allowClear, false, "content.allowClear #3");

assert.equal(q1.showOtherItem, true, "q1.showOtherItem #1");
assert.equal(content.showOtherItem, true, "content.showOtherItem #1");
q1.showOtherItem = false;
assert.equal(q1.showOtherItem, false, "q1.showOtherItem #2");
assert.equal(content.showOtherItem, false, "content.showOtherItem #2");
content.showOtherItem = true;
assert.equal(q1.showOtherItem, true, "q1.showOtherItem #3");
assert.equal(content.showOtherItem, true, "content.showOtherItem #3");
const json = q1.toJSON();
assert.equal(json.allowClear, false, "json.allowClear");
assert.equal(json.showOtherItem, true, "json.showOtherItem");

ComponentCollection.Instance.clear();
});

0 comments on commit ac5dfd1

Please sign in to comment.